redash deploy 部署分析

docker-compose 文件

整个项目主要分为4个容器,Server、Workder、Redis、Postgres。

server:
    build: .
    command: dev_server
    depends_on:
      - postgres
      - redis
    ports:
      - "5000:5000"
    volumes:
      - ".:/app"
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: "INFO"
      REDASH_REDIS_URL: "redis://redis:6379/0"
      REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"

其中Server作为主容器,加载/opt/redash文件夹到./app文件夹。依赖于redis和Postgres两个存储容器。
Server中主要将npm、flask等等进程

worker:
    build: .
    command: scheduler
    volumes_from:
      - server
    depends_on:
      - server
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: "INFO"
      REDASH_REDIS_URL: "redis://redis:6379/0"
      REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
      QUEUES: "queries,scheduled_queries,celery"
      WORKERS_COUNT: 2

Worker主要启动scheduler计划,各种数据库的定时调用计划等等都在worker中执行。

redis:
    image: redis:3.0-alpine
    restart: unless-stopped
postgres:
    image: postgres:9.5.6-alpine
    # The following turns the DB into less durable, but gains significant performance improvements for the tests run (x3
    # improvement on my personal machine). We should consider moving this into a dedicated Docker Compose configuration for
    # tests.
    command: "postgres -c fsync=off -c full_page_writes=off -c synchronous_commit=OFF"
    restart: unless-stopped

数据库采用redis和postgres。

2018/11/9 posted in  python

redash datasouce list

redash在默认安装的时候,导入的是 requirement.txt 和 requirement_dev.txt , 这样只会安装几个基本的DataSource,所有的DataSource的列表包在requirement_all_ds.txt中,安装好之后就可以加载所有的datasouce了。

从安装脚本可以看到加载了所有的Requirement

2018/11/8 posted in  python

Python 关键语句

2018/10/20 posted in  python

django 开发包整理

django-extensions

比较好用的几个工具包含 notes来查看当前django的所有的标注和注释。同时扩展的模型类也比较好用。

django-extensions 这个 Django 包非常受欢迎,全是有用的工具,比如下面这些管理命令:

  • shell_plus 打开 Django 的管理 shell,这个 shell 已经自动导入了所有的数据库模型。在测试复杂的数据关系时,就不需要再从几个不同的应用里做导入操作了。
  • clean_pyc 删除项目目录下所有位置的 .pyc 文件
  • create_template_tags 在指定的应用下,创建模板标签的目录结构。
  • describe_form 输出模型的表单定义,可以粘贴到 forms.py 文件中。(需要注意的是,这种方法创建的是普通 Django 表单,而不是模型表单。)
  • notes 输出你项目里所有带 TODO、FIXME 等标记的注释。

Django-extensions 还包括几个有用的抽象基类,在定义模型时,它们能满足常见的模式。当你需要以下模型时,可以继承这些基类:

  • TimeStampedModel:这个模型的基类包含了 created 字段和 modified 字段,还有一个 save() 方法,在适当的场景下,该方法自动更新 created 和 modified 字段的值。
  • ActivatorModel:如果你的模型需要像 status、activate_date 和 deactivate_date 这样的字段,可以使用这个基类。它还自带了一个启用 .active() 和 .inactive() 查询集的 manager。
  • TitleDescriptionModel 和 TitleSlugDescriptionModel:这两个模型包括了 title 和 description 字段,其中 description 字段还包括 slug,它根据 title 字段自动产生。

django-environ

用于配置当前django的各种不同环境下的配置,很好的解决了在开发和部署情况下面的配置不同。

因子应用配置

在 Django 项目的配置方面,django-environ 提供了符合 12 因子应用 方法论的管理方法。它是另外一些库的集合,包括 envparse 和 honcho 等。安装了 django-environ 之后,在项目的根目录创建一个 .env 文件,用这个文件去定义那些随环境不同而不同的变量,或者需要保密的变量。(比如 API 密钥,是否启用调试,数据库的 URL 等)

然后,在项目的 settings.py 中引入 environ,并参考官方文档的例子设置好 environ.PATH() 和 environ.Env()。就可以通过 env('VARIABLE_NAME') 来获取 .env 文件中定义的变量值了。

处理有限状态机:django-fsm

django-fsm 给 Django 的模型添加了有限状态机的支持。如果你管理一个新闻网站,想用类似于“写作中”、“编辑中”、“已发布”来流转文章的状态,django-fsm 能帮你定义这些状态,还能管理状态变化的规则与限制。

Django-fsm 为模型提供了 FSMField 字段,用来定义模型实例的状态。用 django-fsm 的 @transition 修饰符,可以定义状态变化的方法,并处理状态变化的任何副作用。

虽然 django-fsm 文档很轻量,不过 Django 中的工作流(状态) 这篇 GitHub Gist 对有限状态机和 django-fsm 做了非常好的介绍。

用户注册和认证:django-allauth

django-allauth 是一个 Django 应用,它为用户注册、登录/注销、密码重置,还有第三方用户认证(比如 GitHub 或 Twitter)提供了视图、表单和 URL,支持邮件地址作为用户名的认证方式,而且有大量的文档记录。第一次用的时候,它的配置可能会让人有点晕头转向;请仔细阅读安装说明,在自定义你的配置时要专注,确保启用某个功能的所有配置都用对了。

处理 Django REST 框架的用户认证:django-rest-auth

如果 Django 开发中涉及到对外提供 API,你很可能用到了 Django REST Framework(DRF)。如果你在用 DRF,那么你应该试试 django-rest-auth,它提供了用户注册、登录/注销,密码重置和社交媒体认证的端点(是通过添加 django-allauth 的支持来实现的,这两个包协作得很好)。

Django REST 框架的 API 可视化:django-rest-swagger

Django REST Swagger 提供了一个功能丰富的用户界面,用来和 Django REST 框架的 API 交互。你只需要安装 Django REST Swagger,把它添加到 Django 项目的已安装应用中,然后在 urls.py 中添加 Swagger 的视图和 URL 模式就可以了,剩下的事情交给 API 的 docstring 处理。

参考文章
简化 Django 开发的八个 Python 包

2018/10/20 posted in  python

django中的Datetime

django中时间存储的是UTC格式,在项目中settings.py中配置的TIME_ZONE = 'Asia/Shanghai'并不能改变时间在数据库中的存储形式。

USE_TZ = True 设置了全局是否使用TimeZone模式,如果FALSE的话就会全局使用统一的模式,但是这种在其他有时区的电脑上就会发生错误。

可以看到当USE_TZ使用了True的时候,数据库实际上存储的时间是UTC时间,但是当外部获取的时候,django将所有的时间也使用UTC格式表示,这样就在各个不同的系统中可以获取准确的带时区的UTC时间,时间格式为: 2018-10-15T10:28:10.521988+08:00 这样的时间在外面显示的时候就可以更清楚了。

2018/10/19 posted in  python

微信小程序登陆设计

使用这个流程,所以中间获取token不能使用老的获取token的方法。

检测所有的rest接口,如果token失效就调用登陆获取微信凭证重新刷新token,然后刷新页面,或者跳转到首页两种方式都可以。
当用户token超时的时候,再次调用登陆即可获取新的token。

简单的处理可以使用用户的信息来作为密码,但是这样极不安全。只有用微信的方式最 安全,但是这样需要调用微信服务接口。

2018/10/13 posted in  python

redash 5.0版本DEV环境配置

安装依赖软件

Python (2.7), PostgreSQL (9.3 or newer), Redis (2.8.3 or newer) and Node.js (v6 or newer)

    apt-get -y update
    # Base packages
    apt install -y python-pip python-dev nginx curl build-essential pwgen
    # Data sources dependencies:
    apt install -y libffi-dev libssl-dev libmysqlclient-dev libpq-dev freetds-dev libsasl2-dev
    # SAML dependency
    apt install -y xmlsec1
    # Storage servers
    apt install -y postgresql redis-server
    apt install -y supervisor

安装node 和 npm

apt-get install nodejs
apt-get install npm
apt-get install nodejs-legacy
npm install
npm run build

配置env

在redash目录下增加文件 .env

export REDASH_LOG_LEVEL="INFO"
export REDASH_REDIS_URL=redis://localhost:6379/0
export REDASH_DATABASE_URL="postgresql:///redash"
export REDASH_COOKIE_SECRET=5ceD36XZNRNyo5Y2lzG0MLwsrkYsjoOZ
export REDASH_MAIL_SERVER="smtp.hansap.com"
export REDASH_MAIL_PORT="25"
export REDASH_MAIL_USE_TLS="false"
export REDASH_MAIL_USE_SSL="false"
export REDASH_MAIL_USERNAME="hansap@hansap.com"
export REDASH_MAIL_PASSWORD="Fire@123"
export REDASH_MAIL_DEFAULT_SENDER="hansap@hansap.com"
export ERDASH_HOST="http://bi.hansap.com"

检查设置:

bin/run ./manage.py check_settings

创建数据库

#需要安装redispy
pip install redispy  


adduser --system --no-create-home --disabled-login --gecos "" redash
sudo -u postgres createuser redash --no-superuser --no-createdb --no-createrole
sudo -u postgres createdb redash --owner=redash

sudo -u redash bin/run ./manage.py database create_tables

使用root安装,如果用redash用户安装的话可能会造成无法在virtualenv下面安装的问题

sudo -u postgres createuser root
sudo -u postgres createdb redash --owner=root
sudo -u redash bin/run ./manage.py database create_tables

ubuntu 18 下配置补充

  1. ubuntu 18 默认的posgtresql的版本较高,所以在requirement.txt文件中的psycopg版本最好改到2.2.7.5
  2. ubuntu下requirement.txt的cryptography版本修改到2.2.2版本,否则会报错。
2018/9/30 posted in  python

fullcalendar icalendar 集成

2018/9/15 posted in  python

django-rest格式化和相关方法

格式化

后续所有的Rest请求都采用如下请求模式

1. token

token由于是单独的接口,并使用原生的django-rest-framework接口,所以采用如下的返回模式

{
    "access_token": "JbAornhQy4Qt5nsP1OZreZ4ZrEeyCf",
    "expires_in": 36000,
    "token_type": "Bearer",
    "scope": "read write groups",
    "refresh_token": "3Mb7hKGhTmn71jxeSqaIhUPk92Yics"
}

2. 调用失败情况

{
    "code": 401,
    "desc": "Authentication credentials were not provided."
}

3. 调用成功情况

{
    "code": 200,
    "desc": "get category list success",
    "data": [
        {
            "id": 1,
            "category_name": "活动",
            "category_color": "bg-success"
        },
        {
            "id": 2,
            "category_name": "课程",
            "category_color": "bg-info"
        },
        {
            "id": 3,
            "category_name": "咨询",
            "category_color": "bg-primary"
        }
    ]
}

配置方式

utils.py

from rest_framework.views import exception_handler
from django.utils import six
from rest_framework.response import Response
from rest_framework.serializers import Serializer


def custom_exception_handler(exc, context):
    # Call REST framework's default exception handler first,
    # to get the standard error response.
    response = exception_handler(exc, context)

    # Now add the HTTP status code to the response.
    if response is not None:
        response.data['code'] = response.status_code
        response.data['desc'] = response.data['detail']
        del response.data['detail']  # 删除detail字段

    return response


class JsonResponse(Response):
    """
    An HttpResponse that allows its data to be rendered into
    arbitrary media types.
    """

    def __init__(self, data=None, code=None, desc=None,
                 status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None):
        """
        Alters the init arguments slightly.
        For example, drop 'template_name', and instead use 'data'.
        Setting 'renderer' and 'media_type' will typically be deferred,
        For example being set automatically by the `APIView`.
        """
        super(Response, self).__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)

        self.data = {"code": code, "desc": desc, "data": data}
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in six.iteritems(headers):
                self[name] = value

setting.py中的配置

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'EXCEPTION_HANDLER': (
        'scaffolding.utils.custom_exception_handler'
    )
}

调用中的配置:

@api_view(['GET'])
def category_list(request):
    '''
    查询当前用户可以看到的所有活动目录
    :param request:
    :return:
    '''
    if request.method == 'GET':
        category_list = [];
        if request.user.is_staff:
            category_list = TimeCategory.objects.all()
        else:
            # 后续可以给用户一些专用的category
            category_list = TimeCategory.objects.filter(id=2)
        serializer = CategoryListSerializer(category_list, many=True)
        return JsonResponse(data=serializer.data, code=status.HTTP_200_OK, desc="get category list success")

2018/9/1 posted in  python

Token超时机制,Token刷新怎么做

参考页面:
http://txjdsk.iteye.com/blog/1861210
https://blog.csdn.net/educast/article/details/69230054
https://www.jb51.net/article/84604.htm
https://blog.csdn.net/WongRu1/article/details/80953314
https://www.cnblogs.com/dong-xu/p/6075705.html

解决方法

oauth的token超时之后,用户登陆的session其实还在,只是调用ajax的时候由于token超时而无法获取相关的数据。

解决方法是:

$.ajaxSetup({
            beforeSend: function (xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
                xhr.setRequestHeader('Authorization', 'Bearer ' + $.cookie('access_token'));
            },
            error: function (xhr,textStatus,errorThrown){
                if (xhr.status == 401){
                    window.location.href = "/logout/"
                }
            }
        });

网上比较多的方式说是使用dataFilter来绑定,这种方法不是太适合当前模式的OAuth2。由于dataFilter需要ajax返回成功的时候才FIlter他的data数据,但是这种情况下的用户无权限实际上是返回的error。所以需要通过error来进行绑定。

待完成

这里的token超时之后是直接跳转到logout,然后跳转到login待用户登陆。实际上还可以使用token refresh,这种后面可以研究一下怎么做。

2018/9/1 posted in  python

Adminto_v2.0前端模板使用注释

重点纪要

模态框问题

modal 模态框中写事件要用a标签,不能用button标签,modal绑定了botton事件的关闭模态框,如果使用button来绑定事件的话,事件执行完毕之后会关闭模态框,使用a标签就不会。

advanced-form使用指南

datetime-picker

使用

<link href="{% static 'plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet">
<script src="{% static 'plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>


<div class="form-group row">
    <label class="control-label col-sm-4">Auto Close</label>
    <div class="col-sm-8">
        <div class="input-group">
            <input type="text" class="form-control" placeholder="mm/dd/yyyy" id="datepicker-autoclose">
            <div class="input-group-append">
                <span class="input-group-text"><i class="ti-calendar"></i></span>
            </div>
        </div><!-- input-group -->
    </div>
</div>

jQuery('#datepicker-autoclose').datepicker({
    autoclose: true,
    todayHighlight: true
});

touch-spin

<link href="{% static 'plugins/bootstrap-touchspin/dist/jquery.bootstrap-touchspin.min.css' %}" rel="stylesheet"
          type="text/css"/>
<script src="{% static 'plugins/bootstrap-touchspin/dist/jquery.bootstrap-touchspin.min.js' %}"
            type="text/javascript"></script>          
            
<div class="col-md-8">
         <label class="control-label">星期</label>
          <input type="text" value="" name="day_of_week">
</div>

$("input[name='day_of_week']").TouchSpin({
            initval: 1,
            min: 1,
            max: 7,
            buttondown_class: "btn btn-primary",
            buttonup_class: "btn btn-primary"
        });

2018/8/23 posted in  python

django 集成oauth2

curl -X POST -d "grant_type=password&username=pengjunjie&password=stella" -u"G65f6XQQ1FkqEm5LBMW4PEleBqQxfHdq9WPKjjaA:Pdf9nfta886KT2wGV9hJyN4tn37pSEGGHkXLpqcLYPvu8vzQkLAPBLbNXNyKy3MBHQCQT3vYA9Wniyg4bnFBqrqHJAChshz4WNQyFHOmByu4NA8S6Au6PgjUdWmbFrce" http://localhost:8000/token/

使用 Django OAuth Toolkit 工具包

注意:当前我使用的django 1.11版本,python3.6版本。如果直接使用pip安装django-oauth-toolkit,会默认去下载django2.0版本,所以这里我选择安装了django-oauth-toolkit==1.1.2版本,在这个版本下会默认使用本地的django1.11版本

官网上的操作手册有两个,一个是直接使用的Tutorias,另外一个是django rest framework,最好还是去看django rest framwork的版本,这样结合得更紧密一些。

使用了oauth toolkit,配置完成后,其实对整个django的侵入不算大,依然可以使用request.user来判断用户,这样节省了一个比较大的时间来写公共方法。这样也解决了如果需要绑定用户信息的话,如果不带token就没法使用postman的情况,方便用户长期使用。

接口情况

获取token

curl -X POST -d "grant_type=password&username=pengjunjie&password=stella" -u"l3iNxSydVcHvTeKS9PZJgp2xKgWisaPS60STiWtq:dkyyvEo6rBn2HJRfQThC8Bi5uHVgD2dw8D9r2V5ZsXD8Zrqd0jbhaTXfwgFRZsDZnnLkw6uKra4lrkj6YmeoW8noWRFohMaVXlrADh5wpnNcRSru7prvIZRNwDMJhKMw" http://localhost:8000/token/

在postman上配置如下

查看接口情况

^o/ ^authorize/$ [name='authorize']
^o/ ^token/$ [name='token']
^o/ ^revoke_token/$ [name='revoke-token']
^o/ ^introspect/$ [name='introspect']
^o/ ^applications/$ [name='list']
^o/ ^applications/register/$ [name='register']
^o/ ^applications/(?P<pk>[\w-]+)/$ [name='detail']
^o/ ^applications/(?P<pk>[\w-]+)/delete/$ [name='delete']
^o/ ^applications/(?P<pk>[\w-]+)/update/$ [name='update']
^o/ ^authorized_tokens/$ [name='authorized-token-list']
^o/ ^authorized_tokens/(?P<pk>[\w-]+)/delete/$ [name='authorized-token-delete']

上面就是自带的所有接口情况

2018/7/30 posted in  python

django中同通过getlist() 接收页面form的post数组

前端中的一些东西:

  <input type="text" name="peoName"/>   <input type="text" name="peoName"/>   <input type="text" name="peoName"/>

在后台处理中可以通过以下代码获取参数:

peoNames = request.POST.getlist('peoName',[])
获取到的peoNames是一个数组,遍历就可以得到所有元素的值,注意,request.POST的类型是QueryDict,和普通的Dict不同的是,如果使用request.POST.get方法,只能获得数组的最后一个元素,必须使用getlist才能获取整个数组,以Python列表的形式返回所请求键的数据。 若键不存在则返回空列表。 它保证了一定会返回某种形式的list。

关于django的POST常见方法

1.用post方法去取form表单的值

在取值前,先得判断是否存在这个key

  if not request.POST.has_key(strName):
      return "" 
  if request.POST[strName]:
      return request.POST[strName] 
  else:
      return ""

2.用post方法获取[]类型的数据

常见的,例如,每行数据前面都带个checkbox的操作。这时候可能会选多个checkbox,传入到后台时,如果用request.POST[strname]获取,那么只能获取到一个值。用下面的方法,可以获取到多值

  if not request.POST.has_key(strName):
      return "" 
  if request.POST[strName]:
      return ','.join(request.POST.getlist(strName)) 
  else:
      return ""

2018/7/27 posted in  python

django manytomany 保存和更新

Django中的模型类维护了一对多和多对多的关系。在保存这些关联对象时,只要按照属性字段为其设置相对应的管理对象,然后调用save()方法即可。比如下面代码中,一个Blog对应多个Entry对象;多个Entry对应多个Auther对象:

class Blog(models.Model):
name=models.CharField(max_length=100)
tagline=models.TextField()
def unicode(self):
return self.name
class Author(models.Model):
name=models.CharField(max_length=50)
email=models.EmailField()
def unicode(self):
return self.name

class Entry(models.Model):
blog=models.ForeignKey(Blog)
headline=models.CharField(max_length=255)
body_text=models.TextField()
pub_date=models.DateTimeField()
mod_date=models.DateTimeField()
authors=models.ManyToManyField(Author)
n_comments=models.IntegerField()
n_pingbacks=models.IntegerField()
rating=models.IntegerField()

def __unicode__(self):
    return self.headline

现在我们开始保存Entry对象,先来看如何保存一对多关联对象:

$ python manage.py shell
Python 2.7.1 (r271:86832, Nov 27 2010, 18:30:46) [MSC v.1500 32 bit (Intel)] on
win32
Type “help”, “copyright”, “credits” or “license” for more information.
(InteractiveConsole)

from myapp.models import *
import datetime
e1=Entry(headline=‘Hello’,body_text='Hello World’,pub_date=datetime.datetime
.now(),mod_date=datetime.datetime.now(),n_comments=1,n_pingbacks=1,rating=1) # 创建一个Entry对象
b=Blog.objects.get(pk=1) # 取得一个已经存在的Blog对象
e1.blog=b #设置这个Entry属于取得的Blog
e1.save() # 保存这个Entry对象
由于Blog和Entry之间是一对多关系,所以这里我们保存这个Entry的Blog属性时,只要将它的blog字段赋值即可。
下面再来看看如何保存多对多关系:

eric=Author.objects.create(name='Eric’,email='test@test.cn’) # 用create方法创建一个Author对象
e1.authors.add(eric) #把这个Author加入到Entry中
e1.save() #保存这个Entry对象
保存多对多关系时,需要通过create方法创建一个多对多管理的对象,这里是Author。由于一个Entry有多个Author,所以我们这里用到了add()方法,来把这个Author添加到Entry中。我们在使用create()、get()方法时都是使用了相应对象的objects这个对象。这里的objects是对象的管理器,它是Django为每个模型对象提供的默认管理器对象,需要注意的是,objects对象只能是类级别的对象,而不是实力级别的对象。也就是说如果你构造了一个模型对象的实例,是不能通过实例来获取objects的,如果这样使用会抛出异常。

2018/7/27 posted in  python

CSRF的小技巧

django默认开启了CSRF验证,在form的提交或者rest framework里面的POST的请求的时候,会开启CSRF的验证。

如果用户没有登录的情况下,可以不需要使用csrf就调用,但是如果有用户信息的时候,会经过CSRF,在调用请求的时候可以看到相应的返回。

这一篇blog讲的比较清楚 http://www.liujiangblog.com/course/django/179

如果在javascript的代码中修改,需要增加

// using jQuery
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');

function csrfSafeMethod(method) {
    // 这些HTTP方法不要求CSRF包含
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});
2018/7/1 posted in  python