Commit a9c03b6f authored by Kerwin_Cui's avatar Kerwin_Cui

首次

parents
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>
\ No newline at end of file
<?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="settings.py" />
<option name="manageScript" value="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$">
<sourceFolder url="file://$MODULE_DIR$/templates" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Python 2.7 (cuitest)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/src/frmework/templates" />
<option value="$MODULE_DIR$/templates" />
</list>
</option>
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>
\ No newline at end of file
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="15">
<item index="0" class="java.lang.String" itemvalue="django-crontab" />
<item index="1" class="java.lang.String" itemvalue="rsa" />
<item index="2" class="java.lang.String" itemvalue="paramiko" />
<item index="3" class="java.lang.String" itemvalue="drf-extensions" />
<item index="4" class="java.lang.String" itemvalue="pymssql" />
<item index="5" class="java.lang.String" itemvalue="Django" />
<item index="6" class="java.lang.String" itemvalue="impacket" />
<item index="7" class="java.lang.String" itemvalue="django-jsonfield" />
<item index="8" class="java.lang.String" itemvalue="djangorestframework" />
<item index="9" class="java.lang.String" itemvalue="django-cors-headers" />
<item index="10" class="java.lang.String" itemvalue="django-extensions" />
<item index="11" class="java.lang.String" itemvalue="supervisor" />
<item index="12" class="java.lang.String" itemvalue="suds" />
<item index="13" class="java.lang.String" itemvalue="httplib2" />
<item index="14" class="java.lang.String" itemvalue="MarkupSafe" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 2.7 (cuitest)" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/event.iml" filepath="$PROJECT_DIR$/.idea/event.iml" />
</modules>
</component>
</project>
\ No newline at end of file
This diff is collapsed.
a=(
print tuple(a)
\ No newline at end of file
This diff is collapsed.
蓝鲸智云应用开发模板--助力你的自动化
这里有各种层次的应用样例,根据你的需要,选择合适的样例开始快速开发。
运行要求说明:
# 安装requirements.txt文件中的python包
# 安装mysql,并调整 config\settings_develop.py里的DB用户名和密码
# 数据库初始化: 本模版工程的具体使用过程如下:
1. manage.py migate (初始化数据库表)
2. 针对有表的application创建表:
例如:manage.py migrate app_control
# 在项目文件夹同级的目录里建立logs文件夹(如不清楚可以直接runserver后看错误提示信息)
目录说明:
--公共包
account:用户登录鉴权
app_control:应用功能开关
common:公用方法(log,decorator, context_processors)
static:态文件(css, js, img)
templates:模版(django模版和mako模版,如果说ajax的子页面,模版文件可以使用其他后缀,如**.part)
-- 配置包
conf:用户配置包
settings_development.py:开发环境配置,如 数据库
settings_testing.py: 测试环境配置
settings_production.py:正式环境配置
--应用包
home_application: 你的根应用包,用于开发你的应用的主要功能,子功能可以单独建立其他的应用包
开发说明:
--修改配置文件
conf/default.py 文件:APP_ID \ APP_TOKEN (蓝鲸智云开发者中心 -> 点击应用ID -> 基本信息 中可以查看到这个两个值的信息)
conf/default.py 文件:BK_PAAS_HOST(蓝鲸智云开发者中心的域名,形如:http://paas.bking.com)
conf/settings_development.py 文件:DATABASES(本地开发数据库信息)
conf/settings_testing.py 文件:DATABASES(测试环境数据库信息)
conf/settings_production.py 文件:DATABASES(正式环境数据库信息)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
__author__ = u"蓝鲸智云"
__copyright__ = "Copyright © 2012-2017 Tencent BlueKing. All Rights Reserved."
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
账号体系相关的基类Account.
"""
from django.conf import settings
from django.contrib.auth import logout as auth_logout, get_user_model
from django.contrib.auth.views import redirect_to_login
from django.http import HttpResponse
from django.utils.six.moves.urllib.parse import urlparse
from common.log import logger
from common.mymako import render_mako_context
from account.http import http_get
class AccountSingleton(object):
"""
单例基类.
"""
_instance = None
def __new__(cls, *args, **kwargs):
if not isinstance(cls._instance, cls):
cls._instance = object.__new__(cls, *args, **kwargs)
return cls._instance
class Account(AccountSingleton):
"""
账号体系相关的基类Account.
提供通用的账号功能
"""
# 平台验证用户登录态接口
# BK_LOGIN_VERIFY_URL = "%s/login/accounts/is_login/" % settings.BK_PAAS_HOST
BK_LOGIN_VERIFY_URL = "%s/login/accounts/is_login/" % getattr(settings, 'BK_PAAS_INNER_HOST', settings.BK_PAAS_HOST)
# 平台获取用户信息接口
# BK_GET_USER_INFO_URL = "%s/login/accounts/get_user/" % settings.BK_PAAS_HOST
BK_GET_USER_INFO_URL = "%s/login/accounts/get_user/" % getattr(settings, 'BK_PAAS_INNER_HOST', settings.BK_PAAS_HOST)
def is_bk_token_valid(self, request):
"""验证用户登录态."""
bk_token = request.COOKIES.get(settings.BK_COOKIE_NAME, None)
if not bk_token:
return False, None
ret, data = self.verify_bk_login(bk_token)
# bk_token 无效
if not ret:
return False, None
# 检查用户是否存在用户表中
username = data.get('username', '')
user_model = get_user_model()
try:
user = user_model._default_manager.get_by_natural_key(username)
except user_model.DoesNotExist:
user = user_model.objects.create_user(username)
finally:
try:
ret, data = self.get_bk_user_info(bk_token)
# 若获取用户信息失败,则用户可登录,但用户其他信息为空
user.chname = data.get('chname', '')
user.company = data.get('company', '')
user.qq = data.get('qq', '')
user.phone = data.get('phone', '')
user.email = data.get('email', '')
# 用户权限更新,保持与平台同步
role = data.get('role', '')
is_admin = True if role == '1' else False
user.is_superuser = is_admin
user.is_staff = is_admin
user.save()
except Exception as e:
logger.error(u"获取记录用户信息失败:%s" % e)
return True, user
def verify_bk_login(self, bk_token):
"""请求平台接口验证登录是否失效"""
param = {'bk_token': bk_token}
result, resp = http_get(self.BK_LOGIN_VERIFY_URL, param)
resp = resp if result and resp else {}
ret = resp.get('result', False)
# 验证失败
if not ret:
logger.info(u"验证用户登录token无效:%s" % resp.get('message', ''))
return False, {}
return True, resp.get('data', {})
def get_bk_user_info(self, bk_token):
"""请求平台接口获取用户信息"""
param = {'bk_token': bk_token}
result, resp = http_get(self.BK_GET_USER_INFO_URL, param)
resp = resp if result and resp else {}
ret = resp.get('result', False) if result and resp else False
# 获取用户信息失败
if not ret:
logger.error(u"请求平台接口获取用户信息失败:%s" % resp.get('message', ''))
return False, {}
return True, resp.get('data', {})
def build_callback_url(self, request, jump_url):
callback = request.build_absolute_uri()
login_scheme, login_netloc = urlparse(jump_url)[:2]
current_scheme, current_netloc = urlparse(callback)[:2]
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):
callback = request.get_full_path()
return callback
def _redirect_login(self, request, is_login=True):
"""
跳转平台进行登录
"""
if is_login:
# 登录
callback = self.build_callback_url(request, settings.LOGIN_URL)
else:
# 登出
callback = self.http_referer(request)
return redirect_to_login(callback, settings.LOGIN_URL, settings.REDIRECT_FIELD_NAME)
def redirect_login(self, request):
"""
重定向到登录页面.
登录态验证不通过时调用
"""
# ajax跳401
if request.is_ajax():
return HttpResponse(status=401)
# 非ajax请求 跳转至平台登录
return self._redirect_login(request)
def http_referer(self, request):
"""
获取 HTTP_REFERER 头,得到登出后要重新登录跳转的url
"""
if 'HTTP_REFERER' in request.META:
http_referer = request.META['HTTP_REFERER']
else:
http_referer = settings.LOGIN_REDIRECT_URL
return http_referer
def logout(self, request):
"""登出并重定向到登录页面."""
auth_logout(request)
return self._redirect_login(request, False)
def check_failed(self, request):
"""功能开关检查失败"""
code = request.GET.get('code', '')
# 功能开关检查失败的提示页面
if code == 'func_check':
res_page = '/account/func_check_failed.html'
else:
res_page = '/403.html'
return render_mako_context(request, res_page)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
BK user admin.
"""
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext_lazy as _
from account.models import BkUser
from account.forms import BkUserChangeForm, BkUserCreationForm
class BkUserAdmin(UserAdmin):
"""
The forms to add and change user instances.
The fields to be used in displaying the User model.
These override the definitions on the base UserAdmin
"""
fieldsets = (
(None, {'fields': ('username',)}),
(_('Personal info'), {'fields': ('chname', 'company')}),
(_('Contact info'), {'fields': ('qq', 'phone', 'email')}),
(_('Permissions'), {'fields': ('is_staff', 'is_superuser')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username',)}),
)
form = BkUserChangeForm
add_form = BkUserCreationForm
list_display = ('username', 'chname', 'company', 'is_staff')
list_filter = ('is_staff', 'is_superuser')
search_fields = ('username', 'chname', 'company')
ordering = ('username',)
admin.site.register(BkUser, BkUserAdmin)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
自定义认证类.
"""
from django.contrib.auth.backends import ModelBackend
from account.accounts import Account
class BkBackend(ModelBackend):
"""自定义认证方法."""
def authenticate(self, request):
account = Account()
login_status, user = account.is_bk_token_valid(request)
if not login_status:
return None
return user
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
登录装饰器.
"""
from functools import wraps
from django.utils.decorators import available_attrs
def login_exempt(view_func):
"""登录豁免,被此装饰器修饰的action可以不校验登录."""
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.login_exempt = True
return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
BK user form.
"""
from django import forms
from account.models import BkUser
class BkUserCreationForm(forms.ModelForm):
"""A form that creates a user, with no privileges"""
class Meta:
model = BkUser
fields = ("username",)
def save(self, commit=True):
user = super(BkUserCreationForm, self).save(commit=False)
if commit:
user.save()
return user
class BkUserChangeForm(forms.ModelForm):
"""A form for updating users.
Includes all the fields onthe user,
"""
class Meta:
model = BkUser
fields = ('username',)
def __init__(self, *args, **kwargs):
super(BkUserChangeForm, self).__init__(*args, **kwargs)
f = self.fields.get('user_permissions', None)
if f is not None:
f.queryset = f.queryset.select_related('content_type')
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
请求登录的http基础方法
Rules:
1. POST/DELETE/PUT: json in - json out, 如果resp.json报错, 则是登录接口问题
2. GET带参数 HEAD不带参数
3. 以统一的header头发送请求
"""
import requests
from django.conf import settings
from common.log import logger
def _gen_header():
headers = {
"Content-Type": "application/json",
"X-APP-CODE": settings.APP_ID,
"X-APP-TOKEN": settings.APP_TOKEN,
}
return headers
def _http_request(method, url, headers=None, data=None):
try:
if method == "GET":
resp = requests.get(url=url, headers=headers, params=data , verify=False)
elif method == "HEAD":
resp = requests.head(url=url, headers=headers , verify=False)
elif method == "POST":
resp = requests.post(url=url, headers=headers, json=data , verify=False)
elif method == "DELETE":
resp = requests.delete(url=url, headers=headers, json=data , verify=False)
elif method == "PUT":
resp = requests.put(url=url, headers=headers, json=data , verify=False)
else:
return False, None
except requests.exceptions.RequestException:
logger.exception("login http request error! type: %s, url: %s, data: %s" % (method, url, str(data)))
return False, None
else:
if resp.status_code != 200:
content = resp.content[:100] if resp.content else ''
logger.error("login http request error! type: %s, url: %s, data: %s, response_status_code: %s, response_content: %s" # noqa
% (method, url, str(data), resp.status_code, content))
return False, None
return True, resp.json()
def http_get(url, data):
headers = _gen_header()
return _http_request(method="GET", url=url, headers=headers, data=data)
def http_post(url, data):
headers = _gen_header()
return _http_request(method="POST", url=url, headers=headers, data=data)
def http_delete(url, data):
headers = _gen_header()
return _http_request(method="DELETE", url=url, headers=headers, data=data)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
Login middleware.
"""
from django.contrib.auth import authenticate
from django.middleware.csrf import get_token as get_csrf_token
from account.accounts import Account
class LoginMiddleware(object):
"""Login middleware."""
def process_view(self, request, view, args, kwargs):
"""process_view."""
if getattr(view, 'login_exempt', False):
return None
user = authenticate(request=request)
if user:
request.user = user
get_csrf_token(request)
return None
account = Account()
return account.redirect_login(request)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('auth', '0006_require_contenttypes_0002'),
]
operations = [
migrations.CreateModel(
name='BkUser',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(unique=True, max_length=128, verbose_name='\u7528\u6237\u540d')),
('chname', models.CharField(max_length=254, verbose_name='\u4e2d\u6587\u540d', blank=True)),
('company', models.CharField(max_length=128, verbose_name='\u516c\u53f8', blank=True)),
('qq', models.CharField(max_length=32, verbose_name='QQ\u53f7', blank=True)),
('phone', models.CharField(max_length=64, verbose_name='\u624b\u673a\u53f7', blank=True)),
('email', models.EmailField(max_length=254, verbose_name='\u90ae\u7bb1')),
('is_staff', models.BooleanField(default=False, help_text='\u666e\u901a\u7ba1\u7406\u5458\u53ef\u4ee5\u767b\u5f55\u5230admin', verbose_name='\u666e\u901a\u7ba1\u7406\u5458')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups')),
('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
},
),
]
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
from __future__ import unicode_literals
from django.db import migrations
from django.core import serializers
from django.conf import settings
from account.models import BkUser
def initial_user_data(apps, schema_editor):
try:
admin_username_list = settings.ADMIN_USERNAME_LIST
for username in admin_username_list:
BkUser.objects.create_superuser(username)
except Exception, e:
pass
class Migration(migrations.Migration):
dependencies = [
('account', '0001_initial'),
]
operations = [
migrations.RunPython(initial_user_data),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0002_initial_user_data'),
]
operations = [
migrations.AlterField(
model_name='bkuser',
name='email',
field=models.EmailField(max_length=254, verbose_name='\u90ae\u7bb1', blank=True),
),
]
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
BK user model define.
"""
from django.db import models
from django.utils import timezone
from django.utils.http import urlquote
from django.utils.translation import ugettext_lazy as _
from django.core.mail import send_mail
from django.contrib.auth.models import (AbstractBaseUser, PermissionsMixin, BaseUserManager)
class BkUserManager(BaseUserManager):
"""BK user manager."""
def _create_user(self, username, is_staff, is_superuser, **extra_fields):
"""Create and saves a User with the given username and password."""
if not username:
raise ValueError(u"'The given username must be set")
now = timezone.now()
user = self.model(
username=username,
is_staff=is_staff,
is_superuser=is_superuser,
last_login=now,
date_joined=now, **extra_fields)
user.save(using=self._db)
return user
def create_user(self, username, **extra_fields):
return self._create_user(username, False, False,
**extra_fields)
def create_superuser(self, username, **extra_fields):
return self._create_user(username, True, True,
**extra_fields)
class BkUser(AbstractBaseUser, PermissionsMixin):
"""
BK user.
username and password are required. Other fields are optional.
"""
username = models.CharField(u"用户名", max_length=128, unique=True)
chname = models.CharField(u"中文名", max_length=254, blank=True)
company = models.CharField(u"公司", max_length=128, blank=True)
qq = models.CharField(u"QQ号", max_length=32, blank=True)
phone = models.CharField(u"手机号", max_length=64, blank=True)
email = models.EmailField(u"邮箱", max_length=254, blank=True)
is_staff = models.BooleanField(u"普通管理员", default=False, help_text=u"普通管理员可以登录到admin")
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = BkUserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_absolute_url(self):
return "/users/%s/" % urlquote(self.email)
def get_full_name(self):
"""Return the username plus the chinese name, with a space in between."""
full_name = '%s %s' % (self.username, self.chname)
return full_name.strip()
def get_short_name(self):
"""Return the chinese name for the user."""
return self.chname
def email_user(self, subject, message, from_email=None):
"""Send an email to this User."""
send_mail(subject, message, from_email, [self.email])
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
from django.conf.urls import url
from account import views
urlpatterns = [
url(r'^logout/$', views.logout, name='logout'),
url(r'^check_failed/$', views.check_failed, name='check_failed'), # 权限验证错误页面
]
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
from account.accounts import Account
from account.decorators import login_exempt
@login_exempt
def logout(request):
account = Account()
return account.logout(request)
@login_exempt
def check_failed(request):
"""权限验证错误页面"""
account = Account()
return account.check_failed(request)
app_code: framework
app_name: 开发框架
author: 蓝鲸智云
category: 开发工具
introduction: “开发框架”是蓝鲸智云团队为开发者提供的示例代码,基于此框架,开发者可以快速上手,利用蓝鲸智云集成平台(PaaS)提供的调度引擎、公共组件等模块,构建低成本、免运维的支撑工具和运营系统。
version: 1.0.1
language: python
is_use_celery: True
desktop:
width: 1200
height: 720
is_max: False
date: 2017-09-08 12:00:00
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
__author__ = u"蓝鲸智云"
__copyright__ = "Copyright © 2012-2017 Tencent BlueKing. All Rights Reserved."
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
# import from lib
from django.contrib import admin
# import from apps here
from app_control.models import FunctionController
class FunctionControllerAdmin(admin.ModelAdmin):
"""
功能开关表注册设置
"""
list_display = ('func_code', 'func_name', 'enabled', 'create_time', 'func_developer')
list_filter = ('func_code',)
search_fields = ('func_code',)
admin.site.register(FunctionController, FunctionControllerAdmin)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
@summary: 功能开关装饰器,function_check(功能开关检测)
@usage:
>>> from app_control.decorators import function_check
>>> @function_check('test_func')
>>> def test_func(request):
>>> pass
"""
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import redirect
from django.utils.decorators import available_attrs
from app_control.utils import func_check
try:
from functools import wraps
except ImportError:
from django.utils.functional import wraps # Python 2.4 fallback.
def function_check(func_code):
"""
功能开关装饰器
@param func_code: 功能ID
"""
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
_result, _message = func_check(func_code)
if _result == 1:
return view_func(request, *args, **kwargs)
else:
return _redirect_func_check_failed(request)
return _wrapped_view
return decorator
def _redirect_func_check_failed(request):
"""
跳转功能权限检测失败的提示页面
"""
url = '%saccount/check_failed/?code=func_check' % settings.SITE_URL
if request.is_ajax():
# ajax跳转页面,需要借助settings.js实现页面跳转或redirect跳转。
resp = HttpResponse(status=402, content=url)
return resp
else:
# 非ajax请求,则直接跳转页面
return redirect(url)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='FunctionController',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('func_code', models.CharField(unique=True, max_length=64, verbose_name='\u529f\u80fdcode')),
('func_name', models.CharField(max_length=64, verbose_name='\u529f\u80fd\u540d\u79f0')),
('enabled', models.BooleanField(default=False, help_text='\u63a7\u5236\u529f\u80fd\u662f\u5426\u5bf9\u5916\u5f00\u653e\uff0c\u82e5\u9009\u62e9\uff0c\u5219\u8be5\u529f\u80fd\u5c06\u5bf9\u5916\u5f00\u653e', verbose_name='\u662f\u5426\u5f00\u542f\u8be5\u529f\u80fd')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='\u521b\u5efa\u65f6\u95f4')),
('func_developer', models.TextField(help_text='\u591a\u4e2a\u5f00\u53d1\u8005\u4ee5\u5206\u53f7\u5206\u9694', null=True, verbose_name='\u529f\u80fd\u5f00\u53d1\u8005', blank=True)),
],
options={
'verbose_name': '\u529f\u80fd\u63a7\u5236\u5668',
'verbose_name_plural': '\u529f\u80fd\u63a7\u5236\u5668',
},
),
]
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
from __future__ import unicode_literals
from django.db import migrations
from django.core import serializers
def initial_app_control_data(apps, schema_editor):
try:
# 初始化功能开关数据
func_data = [
{'model': 'app_control.FunctionController', 'fields': {'func_code': 'func_test', 'func_name': u"示例功能"}},
{'model': 'app_control.FunctionController', 'fields': {'func_code': 'create_task', 'func_name': u"创建任务"}},
{'model': 'app_control.FunctionController', 'fields': {'func_code': 'execute_task', 'func_name': u"执行任务"}},
{'model': 'app_control.FunctionController', 'fields': {'func_code': 'tasks', 'func_name': u"任务列表"}},
{'model': 'app_control.FunctionController', 'fields': {'func_code': 'task', 'func_name': u"任务详情"}},
{'model': 'app_control.FunctionController', 'fields': {'func_code': 'pause_task', 'func_name': u"任务暂停"}},
{'model': 'app_control.FunctionController', 'fields': {'func_code': 'terminate_task', 'func_name': u"任务终止"}},
]
func_obj = serializers.deserialize('python', func_data, ignorenonexistent=True)
for obj in func_obj:
obj.save()
except Exception, e:
print e
pass
class Migration(migrations.Migration):
dependencies = [
('app_control', '0001_initial'),
]
operations = [
migrations.RunPython(initial_app_control_data),
]
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
from django.utils import timezone
from django.db import models
from common.log import logger
class FunctionManager(models.Manager):
def func_check(self, func_code):
"""
@summary: 检查改功能是否开放
@param func_code: 功能ID
@return: (True/False, 'message')
"""
try:
enabled = self.get(func_code=func_code).enabled
return (True, int(enabled))
except Exception, e:
logger.error(u"检查改功能是否开放发生异常,错误信息:%s" % e)
return (False, 0)
class FunctionController(models.Model):
"""
功能开启控制器
"""
func_code = models.CharField(u"功能code", max_length=64, unique=True)
func_name = models.CharField(u"功能名称", max_length=64)
enabled = models.BooleanField(u"是否开启该功能", help_text=u"控制功能是否对外开放,若选择,则该功能将对外开放", default=False)
create_time = models.DateTimeField(u"创建时间", default=timezone.now)
func_developer = models.TextField(u"功能开发者", help_text=u"多个开发者以分号分隔", null=True, blank=True)
objects = FunctionManager()
def __unicode__(self):
return self.func_name
class Meta:
app_label = 'app_control'
verbose_name = u"功能控制器"
verbose_name_plural = u"功能控制器"
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
from django.conf.urls import patterns
urlpatterns = patterns(
'app_control.views',
# 定义URL
)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
from app_control.models import FunctionController
def func_check(func_code):
"""
@summary: 检查功能是否开放
@param func_code: 功能ID
@return (1/2/3, message)
#如下 (0, 功能未开启)
(1, 功能已开启)
"""
result, enabled = FunctionController.objects.func_check(func_code)
return (enabled, u"功能已开启" if enabled else u"功能未开启")
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
# Create your views here.
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
__author__ = u"蓝鲸智云"
__copyright__ = "Copyright © 2012-2017 Tencent BlueKing. All Rights Reserved."
有两种方式访问组件,shortcuts或ComponentClient。使用示例如下:
1. 使用shortcuts
1.1 get_client_by_request
from blueking.component.shortcuts import get_client_by_request
# 从环境配置获取APP信息,从request获取当前用户信息
client = get_client_by_request(request)
kwargs = {'bk_biz_id': 1}
result = client.cc.get_app_host_list(kwargs)
1.2 get_client_by_user
from blueking.component.shortcuts import get_client_by_user
# 从环境配置获取APP信息,从user获取当前用户信息,user为User对象或User中username数据
user = 'xxx'
client = get_client_by_user(user)
kwargs = {'bk_biz_id': 1}
result = client.cc.get_app_host_list(kwargs)
2. 使用ComponentClient
from blueking.component.client import ComponentClient
# APP信息
app_code = 'xxx'
app_secret = 'xxx'
# 用户信息
common_args = {'bk_token': 'xxx'}
# APP信息app_code, app_secret如未提供,从环境配置获取
client = ComponentClient(
app_code=app_code,
app_secret=app_secret,
common_args=common_args
)
kwargs = {'bk_biz_id': 1}
result = client.cc.get_app_host_list(kwargs)
# -*- coding: utf-8 -*-
from ..base import ComponentAPI
class CollectionsBkLogin(object):
"""Collections of BK_LOGIN APIS"""
def __init__(self, client):
self.client = client
self.get_all_users = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/bk_login/get_all_users/',
description=u'获取所有用户信息'
)
self.get_batch_users = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/bk_login/get_batch_users/',
description=u'批量获取用户信息'
)
self.get_batch_users_platform_role = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/bk_login/get_batch_users_platform_role/',
description=u'批量获取用户各平台角色信息'
)
self.get_user = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/bk_login/get_user/',
description=u'获取用户信息'
)
self.get_all_user = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/bk_login/get_all_user/',
description=u'获取所有用户信息'
)
self.get_batch_user = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/bk_login/get_batch_user/',
description=u'获取多个用户信息'
)
self.get_batch_user_platform_role = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/bk_login/get_batch_user_platform_role/',
description=u'获取多个用户在平台应用的角色'
)
# -*- coding: utf-8 -*-
from ..base import ComponentAPI
class CollectionsBkPaas(object):
"""Collections of BK_PAAS APIS"""
def __init__(self, client):
self.client = client
self.get_app_info = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/bk_paas/get_app_info/',
description=u'获取应用信息'
)
This diff is collapsed.
# -*- coding: utf-8 -*-
from ..base import ComponentAPI
class CollectionsCMSI(object):
"""Collections of CMSI APIS"""
def __init__(self, client):
self.client = client
self.send_mail = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/cmsi/send_mail/',
description=u'发送邮件'
)
self.send_mp_weixin = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/cmsi/send_mp_weixin/',
description=u'发送公众号微信消息'
)
self.send_qy_weixin = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/cmsi/send_qy_weixin/',
description=u'发送企业微信'
)
self.send_sms = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/cmsi/send_sms/',
description=u'发送短信'
)
self.send_voice_msg = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/cmsi/send_voice_msg/',
description=u'公共语音通知'
)
self.send_weixin = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/cmsi/send_weixin/',
description=u'发送微信消息'
)
# -*- coding: utf-8 -*-
from ..base import ComponentAPI
class CollectionsGSE(object):
"""Collections of GSE APIS"""
def __init__(self, client):
self.client = client
self.get_agent_info = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/gse/get_agent_info/',
description=u'Agent心跳信息查询'
)
self.get_agent_status = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/gse/get_agent_status/',
description=u'Agent在线状态查询'
)
# -*- coding: utf-8 -*-
from ..base import ComponentAPI
class CollectionsJOB(object):
"""Collections of JOB APIS"""
def __init__(self, client):
self.client = client
self.execute_job = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/job/execute_job/',
description=u'启动作业'
)
self.get_cron_list = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_cron_list/',
description=u'查询业务下定时作业信息'
)
self.get_job_detail = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_job_detail/',
description=u'查询作业模板详情'
)
self.get_job_instance_log = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_job_instance_log/',
description=u'根据作业实例ID查询作业执行日志'
)
self.get_job_instance_status = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_job_instance_status/',
description=u'查询作业执行状态'
)
self.get_job_list = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_job_list/',
description=u'查询作业模板'
)
self.get_own_db_account_list = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_own_db_account_list/',
description=u'查询用户有权限的DB帐号列表'
)
self.update_cron_status = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/job/update_cron_status/',
description=u'更新定时作业状态'
)
self.fast_execute_script = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/job/fast_execute_script/',
description=u'快速执行脚本'
)
self.fast_push_file = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/job/fast_push_file/',
description=u'快速分发文件'
)
self.save_cron = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/job/save_cron/',
description=u'新建或保存定时作业'
)
self.change_cron_status = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/job/change_cron_status/',
description=u'更新定时作业状态'
)
self.execute_task = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/job/execute_task/',
description=u'根据作业模板ID启动作业'
)
self.execute_task_ext = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/job/execute_task_ext/',
description=u'启动作业Ext(带全局变量启动)'
)
self.get_agent_status = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/job/get_agent_status/',
description=u'查询Agent状态'
)
self.get_cron = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_cron/',
description=u'查询业务下定时作业信息'
)
self.get_task = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_task/',
description=u'查询作业模板'
)
self.get_task_detail = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_task_detail/',
description=u'查询作业模板详情'
)
self.get_task_ip_log = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_task_ip_log/',
description=u'根据作业实例ID查询作业执行日志'
)
self.get_task_result = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/job/get_task_result/',
description=u'根据作业实例 ID 查询作业执行状态'
)
# -*- coding: utf-8 -*-
from ..base import ComponentAPI
class CollectionsSOPS(object):
"""Collections of SOPS APIS"""
def __init__(self, client):
self.client = client
self.create_task = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/sops/create_task/',
description=u'创建任务'
)
self.get_task_status = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/sops/get_task_status/',
description=u'查询任务或节点状态'
)
self.get_template_info = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/sops/get_template_info/',
description=u'查询单个模板详情'
)
self.get_template_list = ComponentAPI(
client=self.client, method='GET',
path='/api/c/compapi{bk_api_ver}/sops/get_template_list/',
description=u'查询模板列表'
)
self.operate_task = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/sops/operate_task/',
description=u'操作任务'
)
self.query_task_count = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/sops/query_task_count/',
description=u'查询任务分类统计'
)
self.start_task = ComponentAPI(
client=self.client, method='POST',
path='/api/c/compapi{bk_api_ver}/sops/start_task/',
description=u'开始任务'
)
# -*- coding: utf-8 -*-
import json
import logging
from .exceptions import ComponentAPIException
from .conf import COMPONENT_SYSTEM_HOST
logger = logging.getLogger('component')
class ComponentAPI(object):
"""Single API for Component"""
HTTP_STATUS_OK = 200
def __init__(self, client, method, path, description='', default_return_value=None):
host = COMPONENT_SYSTEM_HOST
# Do not use join, use '+' because path may starts with '/'
self.host = host.rstrip('/')
self.path = path
self.url = ''
self.client = client
self.method = method
self.default_return_value = default_return_value
def get_url_with_api_ver(self):
bk_api_ver = self.client.get_bk_api_ver()
sub_path = '/{}'.format(bk_api_ver) if bk_api_ver else ''
return self.host + self.path.format(bk_api_ver=sub_path)
def __call__(self, *args, **kwargs):
self.url = self.get_url_with_api_ver()
try:
return self._call(*args, **kwargs)
except ComponentAPIException, e:
# Combine log message
log_message = [e.error_message, ]
log_message.append('url=%(url)s' % {'url': e.api_obj.url})
if e.resp:
log_message.append('content: %s' % e.resp.text)
logger.exception('\n'.join(log_message))
# Try return error message from remote service
if e.resp is not None:
try:
return e.resp.json()
except:
pass
return {'result': False, 'message': e.error_message, 'data': None}
def _call(self, *args, **kwargs):
params, data = {}, {}
if args and isinstance(args[0], dict):
params = args[0]
params.update(kwargs)
# Validate params for POST request
if self.method == 'POST':
data = params
params = None
try:
json.dumps(data)
except Exception:
raise ComponentAPIException(self, 'Request parameter error (please pass in a dict or json string)')
# Request remote server
try:
resp = self.client.request(self.method, self.url, params=params, data=data)
except Exception, e:
logger.exception('Error occurred when requesting method=%s url=%s',
self.method, self.url)
raise ComponentAPIException(self, u'Request component error, Exception: %s' % str(e))
# Parse result
if resp.status_code != self.HTTP_STATUS_OK:
message = 'Request component error, status_code: %s' % resp.status_code
raise ComponentAPIException(self, message, resp=resp)
try:
# Parse response
json_resp = resp.json()
if not json_resp['result']:
# 组件返回错误时,记录相应的 request_id
log_message = (u'Component return error message: %(message)s, request_id=%(request_id)s, '
u'url=%(url)s, params=%(params)s, data=%(data)s, response=%(response)s') % {
'request_id': json_resp.get('request_id'),
'message': json_resp['message'],
'url': self.url,
'params': params,
'data': data,
'response': resp.text,
}
logger.error(log_message)
# Return default return value
if not json_resp and self.default_return_value is not None:
return self.default_return_value
return json_resp
except:
raise ComponentAPIException(
self, 'Return data format is incorrect, which shall be unified as json', resp=resp)
# -*- coding: utf-8 -*-
"""Component API Client
"""
import requests
import json
import time
import random
import logging
import urlparse
from . import conf
from . import collections
from .utils import get_signature
# shutdown urllib3's warning
try:
requests.packages.urllib3.disable_warnings()
except:
pass
logger = logging.getLogger('component')
class BaseComponentClient(object):
"""Base client class for component"""
@classmethod
def setup_components(cls, components):
cls.available_collections = components
def __init__(self, app_code=None, app_secret=None, common_args=None, use_test_env=False, language=None,
bk_app_code=None, bk_app_secret=None):
"""
:param str app_code: App code to use
:param str app_secret: App secret to use
:param dict common_args: Args that will apply to every request
:param bool use_test_env: whether use test version of components
"""
self.app_code = bk_app_code or app_code or conf.APP_CODE
self.app_secret = bk_app_secret or app_secret or conf.SECRET_KEY
self.bk_api_ver = conf.DEFAULT_BK_API_VER
self.common_args = common_args or {}
self._cached_collections = {}
self.use_test_env = use_test_env
self.language = language or self.get_cur_language()
def set_use_test_env(self, use_test_env):
"""Change the value of use_test_env
:param bool use_test_env: whether use test version of components
"""
self.use_test_env = use_test_env
def set_language(self, language):
self.language = language
def get_cur_language(self):
try:
from django.utils import translation
return translation.get_language()
except:
return None
def set_bk_api_ver(self, bk_api_ver):
self.bk_api_ver = bk_api_ver
def get_bk_api_ver(self):
return self.bk_api_ver
def merge_params_data_with_common_args(self, method, params, data, enable_app_secret=False):
"""get common args when request
"""
common_args = dict(bk_app_code=self.app_code, **self.common_args)
if enable_app_secret:
common_args['bk_app_secret'] = self.app_secret
if method == 'GET':
_params = common_args.copy()
_params.update(params or {})
params = _params
elif method == 'POST':
_data = common_args.copy()
_data.update(data or {})
data = json.dumps(_data)
return params, data
def request(self, method, url, params=None, data=None, **kwargs):
"""Send request
"""
# determine whether access test environment of third-party system
headers = kwargs.pop('headers', {})
if self.use_test_env:
headers['x-use-test-env'] = '1'
if self.language:
headers['blueking-language'] = self.language
params, data = self.merge_params_data_with_common_args(method, params, data, enable_app_secret=True)
logger.debug('Calling %s %s with params=%s, data=%s, headers=%s', method, url, params, data, headers)
return requests.request(method, url, params=params, data=data, verify=False,
headers=headers, **kwargs)
def __getattr__(self, key):
if key not in self.available_collections:
return getattr(super(BaseComponentClient, self), key)
if key not in self._cached_collections:
collection = self.available_collections[key]
self._cached_collections[key] = collection(self)
return self._cached_collections[key]
class ComponentClientWithSignature(BaseComponentClient):
"""Client class for component with signature"""
def request(self, method, url, params=None, data=None, **kwargs):
"""Send request, will add "signature" parameter.
"""
# determine whether access test environment of third-party system
headers = kwargs.pop('headers', {})
if self.use_test_env:
headers['x-use-test-env'] = '1'
if self.language:
headers['blueking-language'] = self.language
params, data = self.merge_params_data_with_common_args(method, params, data, enable_app_secret=False)
if method == 'POST':
params = {}
url_path = urlparse.urlparse(url).path
# signature always in GET params
params.update({
'bk_timestamp': int(time.time()),
'bk_nonce': random.randint(1, 2147483647),
})
params['bk_signature'] = get_signature(method, url_path, self.app_secret, params=params, data=data)
logger.debug('Calling %s %s with params=%s, data=%s', method, url, params, data)
return requests.request(method, url, params=params, data=data, verify=False,
headers=headers, **kwargs)
# 根据是否开启signature来判断使用的Client版本
if conf.CLIENT_ENABLE_SIGNATURE:
ComponentClient = ComponentClientWithSignature
else:
ComponentClient = BaseComponentClient
ComponentClient.setup_components(collections.AVAILABLE_COLLECTIONS)
# -*- coding: utf-8 -*-
"""Collections for component client"""
from .apis.bk_login import CollectionsBkLogin
from .apis.bk_paas import CollectionsBkPaas
from .apis.cc import CollectionsCC
from .apis.cmsi import CollectionsCMSI
from .apis.gse import CollectionsGSE
from .apis.job import CollectionsJOB
from .apis.sops import CollectionsSOPS
# Available components
AVAILABLE_COLLECTIONS = {
'bk_login': CollectionsBkLogin,
'bk_paas': CollectionsBkPaas,
'cc': CollectionsCC,
'cmsi': CollectionsCMSI,
'gse': CollectionsGSE,
'job': CollectionsJOB,
'sops': CollectionsSOPS,
}
# -*- coding: utf-8 -*-
"""Django project settings
"""
try:
from django.conf import settings
APP_CODE = settings.APP_ID
SECRET_KEY = settings.APP_TOKEN
# COMPONENT_SYSTEM_HOST = settings.BK_PAAS_HOST
COMPONENT_SYSTEM_HOST = getattr(settings,'BK_PAAS_INNER_HOST',settings.BK_PAAS_HOST)
DEFAULT_BK_API_VER = getattr(settings, 'DEFAULT_BK_API_VER', 'v2')
except:
APP_CODE = ''
SECRET_KEY = ''
COMPONENT_SYSTEM_HOST = ''
DEFAULT_BK_API_VER = 'v2'
CLIENT_ENABLE_SIGNATURE = False
# -*- coding: utf-8 -*-
class ComponentBaseException(Exception):
pass
class ComponentAPIException(ComponentBaseException):
"""Exception for Component API"""
def __init__(self, api_obj, error_message, resp=None):
self.api_obj = api_obj
self.error_message = error_message
self.resp = resp
if self.resp is not None:
error_message = '%s, resp=%s' % (error_message, self.resp.text)
super(ComponentAPIException, self).__init__(error_message)
# -*- coding: utf-8 -*-
import logging
from .client import ComponentClient
from . import conf
logger = logging.getLogger('component')
__all__ = [
'get_client_by_request',
'get_client_by_user',
]
def get_client_by_request(request, **kwargs):
"""根据当前请求返回一个client
:param request: 一个django request实例
:returns: 一个初始化好的ComponentClint对象
"""
if request.user.is_authenticated():
bk_token = request.COOKIES.get('bk_token', '')
else:
bk_token = ''
common_args = {
'bk_token': bk_token,
}
common_args.update(kwargs)
return ComponentClient(conf.APP_CODE, conf.SECRET_KEY, common_args=common_args)
def get_client_by_user(user, **kwargs):
"""根据user实例返回一个client
:param user: User实例或者User.username数据
:returns: 一个初始化好的ComponentClint对象
"""
try:
from account.models import BkUser as User
except:
from django.contrib.auth.models import User
try:
if isinstance(user, User):
username = user.username
else:
username = user
except:
logger.exception('Failed to get user according to user (%s)' % user)
common_args = {'bk_username': username}
common_args.update(kwargs)
return ComponentClient(conf.APP_CODE, conf.SECRET_KEY, common_args=common_args)
# -*- coding: utf-8 -*-
import json
import base64
import hmac
import hashlib
def get_signature(method, path, app_secret, params=None, data=None):
"""generate signature
"""
kwargs = {}
if params:
kwargs.update(params)
if data:
data = json.dumps(data) if isinstance(data, dict) else data
kwargs['data'] = data
kwargs = '&'.join([
'%s=%s' % (k, v)
for k, v in sorted(kwargs.iteritems(), key=lambda x: x[0])
])
orignal = '%s%s?%s' % (method, path, kwargs)
signature = base64.b64encode(hmac.new(str(app_secret), orignal, hashlib.sha1).digest())
return signature
# -*- coding: utf-8 -*-
import json
from django.test import TestCase
from blueking.component.shortcuts import ComponentClient
class TestAPI(TestCase):
def setUp(self):
self.client = ComponentClient(
bk_app_code='test',
bk_app_secret='xxx',
common_args={
'bk_username': 'admin',
}
)
self.client.set_bk_api_ver('v2')
def test_api(self):
params = {
'bk_biz_id': 1,
}
result = self.client.job.get_job_list(params)
print json.dumps(result)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
__author__ = u"蓝鲸智云"
__copyright__ = "Copyright © 2012-2017 Tencent BlueKing. All Rights Reserved."
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
context_processor for common(setting)
除setting外的其他context_processor内容,均采用组件的方式(string)
"""
from django.conf import settings
import datetime
def mysetting(request):
return {
# 基础信息
'RUN_MODE': settings.RUN_MODE,
'APP_ID': settings.APP_ID,
'SITE_URL': settings.SITE_URL,
# 静态资源
'STATIC_URL': settings.STATIC_URL,
'STATIC_VERSION': settings.STATIC_VERSION,
# 登录跳转链接
'LOGIN_URL': settings.LOGIN_URL,
'LOGOUT_URL': settings.LOGOUT_URL,
'BK_PAAS_HOST': '%s/app/list/' % settings.BK_PAAS_HOST,
'BK_PLAT_HOST': settings.BK_PAAS_HOST,
# 当前页面,主要为了login_required做跳转用
'APP_PATH': request.get_full_path(),
'NOW': datetime.datetime.now(),
}
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
@summary: 装饰器
@usage:
>>> from common.decorators import escape_exempt, escape_script, escape_url
>>> @escape_exempt()
>>> @escape_script()
>>> @escape_url()
>>> def test_func(request):
>>> pass
"""
from django.utils.decorators import available_attrs
try:
from functools import wraps
except ImportError:
from django.utils.functional import wraps # Python 2.4 fallback.
# ===============================================================================
# 转义装饰器
# ===============================================================================
def escape_exempt(view_func):
"""
转义豁免,被此装饰器修饰的action可以不进行中间件escape
"""
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.escape_exempt = True
return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
def escape_texteditor(view_func):
"""
被此装饰器修饰的action会对GET与POST参数作为富文本编辑内容处理
"""
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.escape_script = True
return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
def escape_url(view_func):
"""
被此装饰器修饰的action会对GET与POST参数进行url escape
"""
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.escape_url = True
return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
Usage:
from common.log import logger
logger.info("test")
logger.error("wrong1")
logger.exception("wrong2")
# with traceback
try:
1 / 0
except Exception:
logger.exception("wrong3")
"""
import logging
logger = logging.getLogger("root")
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
import re
import json
from settings import SITE_URL
from common.log import logger
from common.utils import html_escape, url_escape, texteditor_escape
class CheckXssMiddleware(object):
"""
XSS攻击统一处理中间件
"""
def process_view(self, request, view, args, kwargs):
"""
请求参数统一处理
"""
try:
# 判断豁免权
if getattr(view, 'escape_exempt', False):
return None
# 判断豁免
escape_type = None
if getattr(view, 'escape_texteditor', False):
escape_type = 'texteditor'
elif getattr(view, 'escape_url', False):
escape_type = 'url'
# get参数转换
request.GET = self.__escape_data(request.path, request.GET, escape_type)
# post参数转换
request.POST = self.__escape_data(request.path, request.POST, escape_type)
except Exception, e:
logger.error(u"CheckXssMiddleware 转换失败!错误信息:%s" % e)
return None
def __escape_data(self, path, query_dict, escape_type=None):
"""
GET/POST参数转义
"""
data_copy = query_dict.copy()
for _get_key, _get_value_list in data_copy.lists():
new_value_list = []
for _get_value in _get_value_list:
new_value = _get_value
# json串不进行转义
try:
json.loads(_get_value)
is_json = True
except:
is_json = False
# 转义新数据
if not is_json:
if escape_type is None:
use_type = self.__filter_param(path, _get_key)
else:
use_type = escape_type
if use_type == 'url':
new_value = url_escape(_get_value)
elif use_type == 'texteditor':
new_value = texteditor_escape(_get_value)
else:
new_value = html_escape(_get_value)
else:
new_value = html_escape(_get_value, True)
new_value_list.append(new_value)
data_copy.setlist(_get_key, new_value_list)
return data_copy
def __filter_param(self, path, param):
"""
特殊path处理
@param path: 路径
@param param: 参数
@return: 'url/texteditor'
"""
use_url_paths, use_texteditor_paths = self.__filter_path_list()
result = self.__check_escape_type(path, param, use_url_paths, 'url')
# 富文本内容过滤
if result == 'html':
result = self.__check_escape_type(path, param, use_texteditor_paths, 'texteditor')
return result
def __check_escape_type(self, path, param, check_path_list, escape_type):
"""
判断过滤类型
@param path: 请求Path
@param param: 请求参数
@param check_path_list: 指定类型Path列表
@param escape_type: 判断过滤类型
@param result_type: 结果类型
"""
try:
result_type = 'html'
for script_path, script_v in check_path_list.items():
is_path = re.match(r'^%s' % script_path, path)
if is_path and param in script_v:
result_type = escape_type
break
except Exception, e:
logger.error(u"CheckXssMiddleware 特殊path处理失败!错误信息%s" % e)
return result_type
def __filter_path_list(self):
"""
特殊path注册
注册格式:{'path1': [param1, param2], 'path2': [param1, param2]}
"""
use_url_paths = {
'%saccounts/login' % SITE_URL: ['next'],
'%saccounts/login_page' % SITE_URL: ['req_url'],
'%saccounts/login_success' % SITE_URL: ['req_url'],
'%s' % SITE_URL: ['url'],
}
use_texteditor_paths = {}
return (use_url_paths, use_texteditor_paths)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
mako模板的render方法等
"""
import json
from django.conf import settings
from django.http import HttpResponse
from django.template.context import Context
from mako.lookup import TemplateLookup
from common.log import logger
"""
mylookup 也可以单独使用:
@Example:
mako_temp = mylookup.get_template(template_name)
mako_temp.render(**data)
"""
mylookup = TemplateLookup(directories=settings.MAKO_TEMPLATE_DIR,
module_directory=settings.MAKO_TEMPLATE_MODULE_DIR,
output_encoding='utf-8',
input_encoding='utf-8',
encoding_errors='replace',
collection_size=500,
)
def render_mako(template_name, dictionary={}, context_instance=None):
"""
render the mako template and return the HttpResponse
@param template_name: 模板名字
@param dictionary: context字典
@param context_instance: 初始化context,如果要使用 TEMPLATE_CONTEXT_PROCESSORS,则要使用RequestContext(request)
@note: 因为返回是HttpResponse,所以这个方法也适合给ajax用(dataType不是json的ajax)
@Example:
render_mako('mako_temp.html',{'form':form})
or
render_mako('mako_temp.html',{'form':form},RequestContext(request))
or
render_mako('mako_temp.html',{},RequestContext(request,{'form':form}))
"""
mako_temp = mylookup.get_template(template_name)
if context_instance:
# RequestContext(request)
context_instance.update(dictionary)
else:
# 默认为Context
context_instance = Context(dictionary)
data = {}
# construct date dictory
for d in context_instance:
data.update(d)
# return response
return HttpResponse(mako_temp.render_unicode(**data)) # .replace('\r','').replace('\n','').replace('\t','')
def render_mako_context(request, template_name, dictionary={}):
"""
render the mako template with the RequestContext and return the HttpResponse
"""
context_instance = get_context_processors_content(request)
# ===========================================================================
# # you can add csrf_token here
# from django.core.context_processors import csrf
# context_instance['csrf_token'] = csrf(request)['csrf_token']
# ===========================================================================
# render
return render_mako(template_name, dictionary=dictionary, context_instance=context_instance)
def render_mako_tostring(template_name, dictionary={}, context_instance=None):
"""
render_mako_tostring without RequestContext
@note: 因为返回是string,所以这个方法适合include的子页面用
"""
mako_temp = mylookup.get_template(template_name)
if context_instance:
# RequestContext(request)
context_instance.update(dictionary)
else:
# 默认为Context
context_instance = Context(dictionary)
data = {}
# construct date dictory
for d in context_instance:
data.update(d)
# return string
return mako_temp.render_unicode(**data) # .replace('\t','').replace('\n','').replace('\r','')
def render_mako_tostring_context(request, template_name, dictionary={}):
"""
render_mako_tostring with RequestContext
"""
context_instance = get_context_processors_content(request)
return render_mako_tostring(template_name, dictionary=dictionary, context_instance=context_instance)
def render_json(dictionary={}):
"""
return the json string for response
@summary: dictionary也可以是string, list数据
@note: 返回结果是个dict, 请注意默认数据格式:
{'result': '',
'message':''
}
"""
if type(dictionary) is not dict:
# 如果参数不是dict,则组合成dict
dictionary = {
'result': True,
'message': dictionary,
}
return HttpResponse(json.dumps(dictionary), content_type='application/json')
def get_context_processors_content(request):
"""
return the context_processors dict context
"""
context = Context()
try:
from django.utils.module_loading import import_string
from django.template.context import _builtin_context_processors
context_processors = _builtin_context_processors
for i in settings.TEMPLATES:
context_processors += tuple(i.get('OPTIONS', {}).get('context_processors', []))
cp_func_list = tuple(import_string(path) for path in context_processors)
for processors in cp_func_list:
context.update(processors(request))
except Exception, e:
logger.error(u"Mako: get_context_processors_content error info:%s" % e)
context = Context()
return context
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
Python 富文本XSS过滤类
@package XssHtml
@version 0.1
@link http://phith0n.github.io/python-xss-filter
@since 20150407
@copyright (c) Phithon All Rights Reserved
Based on native Python module HTMLParser purifier of HTML, To Clear all javascript in html
You can use it in all python web framework
Written by Phithon <root@leavesongs.com> in 2015 and placed in the public domain.
phithon <root@leavesongs.com> 编写于20150407
From: XDSEC <www.xdsec.org> & 离别歌 <www.leavesongs.com>
GitHub Pages: https://github.com/phith0n/python-xss-filter
Usage:
parser = XssHtml()
parser.feed('<html code>')
parser.close()
html = parser.get_html()
print html
Requirements
Python 2.6+ or 3.2+
Cannot defense xss in browser which is belowed IE7
浏览器版本:IE7+ 或其他浏览器,无法防御IE6及以下版本浏览器中的XSS
"""
import re
from HTMLParser import HTMLParser
class XssHtml(HTMLParser):
allow_tags = ['a', 'img', 'br', 'strong', 'b', 'code', 'pre',
'p', 'div', 'em', 'span', 'h1', 'h2', 'h3', 'h4',
'h5', 'h6', 'blockquote', 'ul', 'ol', 'tr', 'th', 'td',
'hr', 'li', 'u', 'embed', 's', 'table', 'thead', 'tbody',
'caption', 'small', 'q', 'sup', 'sub']
common_attrs = ["id", "style", "class", "name"]
nonend_tags = ["img", "hr", "br", "embed"]
tags_own_attrs = {
"img": ["src", "width", "height", "alt", "align"],
"a": ["href", "target", "rel", "title"],
"embed": ["src", "width", "height", "type", "allowfullscreen", "loop", "play", "wmode", "menu"],
"table": ["border", "cellpadding", "cellspacing"],
}
def __init__(self, allows=[]):
HTMLParser.__init__(self)
self.allow_tags = allows if allows else self.allow_tags
self.result = []
self.start = []
self.data = []
def get_html(self):
"""
Get the safe html code
"""
for i in range(0, len(self.result)):
tmp = self.result[i].rstrip('\n')
tmp = tmp.lstrip('\n')
if tmp:
self.data.append(tmp)
return ''.join(self.data)
def handle_startendtag(self, tag, attrs):
self.handle_starttag(tag, attrs)
def handle_starttag(self, tag, attrs):
if tag not in self.allow_tags:
return
end_diagonal = ' /' if tag in self.nonend_tags else ''
if not end_diagonal:
self.start.append(tag)
attdict = {}
for attr in attrs:
attdict[attr[0]] = attr[1]
attdict = self.__wash_attr(attdict, tag)
if hasattr(self, "node_%s" % tag):
attdict = getattr(self, "node_%s" % tag)(attdict)
else:
attdict = self.node_default(attdict)
attrs = []
for (key, value) in attdict.items():
attrs.append('%s="%s"' % (key, self.__htmlspecialchars(value)))
attrs = (' ' + ' '.join(attrs)) if attrs else ''
self.result.append('<' + tag + attrs + end_diagonal + '>')
def handle_endtag(self, tag):
if self.start and tag == self.start[len(self.start) - 1]:
self.result.append('</' + tag + '>')
self.start.pop()
def handle_data(self, data):
self.result.append(self.__htmlspecialchars(data))
def handle_entityref(self, name):
if name.isalpha():
self.result.append("&%s;" % name)
def handle_charref(self, name):
if name.isdigit():
self.result.append("&#%s;" % name)
def node_default(self, attrs):
attrs = self.__common_attr(attrs)
return attrs
def node_a(self, attrs):
attrs = self.__common_attr(attrs)
attrs = self.__get_link(attrs, "href")
attrs = self.__set_attr_default(attrs, "target", "_blank")
attrs = self.__limit_attr(attrs, {
"target": ["_blank", "_self"]
})
return attrs
def node_embed(self, attrs):
attrs = self.__common_attr(attrs)
attrs = self.__get_link(attrs, "src")
attrs = self.__limit_attr(attrs, {
"type": ["application/x-shockwave-flash"],
"wmode": ["transparent", "window", "opaque"],
"play": ["true", "false"],
"loop": ["true", "false"],
"menu": ["true", "false"],
"allowfullscreen": ["true", "false"]
})
attrs["allowscriptaccess"] = "never"
attrs["allownetworking"] = "none"
return attrs
def __true_url(self, url):
prog = re.compile(r"^(http|https|ftp)://.+", re.I | re.S)
if prog.match(url):
return url
else:
return "http://%s" % url
def __true_style(self, style):
if style:
style = re.sub(r"(\\|&#|/\*|\*/)", "_", style)
style = re.sub(r"e.*x.*p.*r.*e.*s.*s.*i.*o.*n", "_", style)
return style
def __get_style(self, attrs):
if "style" in attrs:
attrs["style"] = self.__true_style(attrs.get("style"))
return attrs
def __get_link(self, attrs, name):
if name in attrs:
attrs[name] = self.__true_url(attrs[name])
return attrs
def __wash_attr(self, attrs, tag):
if tag in self.tags_own_attrs:
other = self.tags_own_attrs.get(tag)
else:
other = []
if attrs:
for (key, value) in attrs.items():
if key not in self.common_attrs + other:
del attrs[key]
return attrs
def __common_attr(self, attrs):
attrs = self.__get_style(attrs)
return attrs
def __set_attr_default(self, attrs, name, default=''):
if name not in attrs:
attrs[name] = default
return attrs
def __limit_attr(self, attrs, limit={}):
for (key, value) in limit.items():
if key in attrs and attrs[key] not in value:
del attrs[key]
return attrs
def __htmlspecialchars(self, html):
return html.replace("<", "&lt;")\
.replace(">", "&gt;")\
.replace('"', "&quot;")\
.replace("'", "&#039;")
if "__main__" == __name__:
parser = XssHtml()
parser.feed("""<p><img src=1 onerror=alert(/xss/)></p><div class="left">
<a href='javascript:prompt(1)'><br />hehe</a></div>
<p id="test" onmouseover="alert(1)">&gt;M<svg>
<a href="https://www.baidu.com" target="self">MM</a></p>
<embed src='javascript:alert(/hehe/)' allowscriptaccess=always />""")
parser.close()
print(parser.get_html())
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
开发框架公用方法
1. 页面输入内容转义(防止xss攻击)
from common.utils import html_escape, url_escape, texteditor_escape
2. 转义html内容
html_content = html_escape(input_content)
3. 转义url内容
url_content = url_escape(input_content)
4. 转义富文本内容
texteditor_content = texteditor_escape(input_content)
"""
from common.pxfilter import XssHtml
from common.log import logger
def html_escape(html, is_json=False):
"""
Replace special characters "&", "<" and ">" to HTML-safe sequences.
If the optional flag quote is true, the quotation mark character (")
is also translated.
rewrite the cgi method
@param html: html代码
@param is_json: 是否为json串(True/False) ,默认为False
"""
# &转换
if not is_json:
html = html.replace("&", "&amp;") # Must be done first!
# <>转换
html = html.replace("<", "&lt;")
html = html.replace(">", "&gt;")
# 单双引号转换
if not is_json:
html = html.replace(' ', "&nbsp;")
html = html.replace('"', "&quot;")
html = html.replace("'", "&#39;")
return html
def url_escape(url):
url = url.replace("<", "")
url = url.replace(">", "")
url = url.replace(' ', "")
url = url.replace('"', "")
url = url.replace("'", "")
return url
def texteditor_escape(str_escape):
"""
富文本处理
@param str_escape: 要检测的字符串
"""
try:
parser = XssHtml()
parser.feed(str_escape)
parser.close()
return parser.get_html()
except Exception, e:
logger.error(u"js脚本注入检测发生异常,错误信息:%s" % e)
return str_escape
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
__author__ = u"蓝鲸智云"
__copyright__ = "Copyright © 2012-2017 Tencent BlueKing. All Rights Reserved."
This diff is collapsed.
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
用于本地开发环境的全局配置
"""
from settings import APP_ID
# ===============================================================================
# 数据库设置, 本地开发数据库设置
# ===============================================================================
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.mysql', # 默认用mysql
# 'NAME': 'event_analysis_new', # 数据库名 (默认与APP_ID相同)
# 'USER': 'root', # 你的数据库user
# 'PASSWORD': 'zorkdata.8888', # 你的数据库password
# 'HOST': '10.187.113.112', # 开发的时候,使用localhost
# 'PORT': '3306', # 默认3306
# }
# }
# 本地数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 默认用mysql
'NAME': APP_ID, # 数据库名 (默认与APP_ID相同)
'USER': 'root', # 你的数据库user
'PASSWORD': 'zork.8888', # 你的数据库password
'HOST': '192.168.3.218', # 开发的时候,使用localhost
'PORT': '3306', # 默认3306
},
}
DATABASE_CONNECTION_POOLING = False
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
用于正式环境的全局配置
"""
from settings import APP_ID
# ===============================================================================
# 数据库设置, 正式环境数据库设置
# ===============================================================================
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 默认用mysql
'NAME': 'event_analysis_new', # 数据库名 (默认与APP_ID相同)
'USER': 'root', # 你的数据库user
'PASSWORD': 'zorkdata.8888', # 你的数据库password
'HOST':'10.240.5.231', # 开发的时候,使用localhost
'PORT': '3306', # 默认3306
}
}
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
用于测试环境的全局配置
"""
from settings import APP_ID
# ===============================================================================
# 数据库设置, 测试环境数据库设置
# ===============================================================================
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 默认用mysql
'NAME': 'event_analysis_new', # 数据库名 (默认与APP_ID相同)
'USER': 'root', # 你的数据库user
'PASSWORD': 'zorkdata.8888', # 你的数据库password
'HOST': '192.168.1.157', # 开发的时候,使用localhost
'PORT': '3306', # 默认3306
},
}
404
500
401
403
等提示页面自定义
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
__author__ = u"蓝鲸智云"
__copyright__ = "Copyright © 2012-2017 Tencent BlueKing. All Rights Reserved."
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
# import from apps here
# import from lib
# ===============================================================================
# from django.contrib import admin
# from apps.__.models import aaaa
#
# admin.site.register(aaaa)
# ===============================================================================
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
# import from apps here
# import from lib
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
# -*- coding: utf-8 -*-
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
"""
from common.mymako import render_mako_context
def error_404(request):
"""
404提示页
"""
return render_mako_context(request, '404.html')
def error_500(request):
"""
500提示页
"""
return render_mako_context(request, '500.html')
def error_401(request):
"""
401提示页
"""
return render_mako_context(request, '401.html')
def error_403(request):
"""
403提示页
"""
return render_mako_context(request, '403.html')
from django.contrib import admin
# Register your models here.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
from django.test import TestCase
# Create your tests here.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
1567267200
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment