Selaa lähdekoodia

Merge branch 'master' into modernize-Python-2-codes

huamanshu 6 vuotta sitten
vanhempi
commit
2990374d79

+ 81 - 15
tests/test_00_base.py

@@ -4,19 +4,37 @@
 from copy import deepcopy
 
 import pytest
-from walle.model.user import MenuModel
+from utils import *
+from walle.model.menu import MenuModel
 from walle.model.user import UserModel
-from werkzeug.security import generate_password_hash
 from walle.service.rbac.role import *
+from werkzeug.security import generate_password_hash
+
+#: 1 创建 super, owner
+
+#: 2 登录 super
 
-user_data_login = {
-    'username': u'wushuiyong',
-    'email': u'wushuiyong@walle-web.io',
+#: 3 创建 space, users
+
+#: 4 登录 owner
+
+user_super = {
+    'username': u'super',
+    'email': u'Super@walle-web.io',
     'password': u'WU123shuiyong',
 }
+
+user_owner = {
+    'username': u'owner',
+    'email': u'Owner@walle-web.io',
+    'password': u'WU123shuiyong',
+}
+
+user_data_login = user_owner
+
 space_base = {
     'name': u'walle-2.0',
-    'user_id': u'1',
+    'user_id': 1,
 }
 
 
@@ -520,20 +538,68 @@ class TestAccess:
             access.save()
 
 
+#: 1 创建 super, owner
 class TestUser:
-    user_super_login = deepcopy(user_data_login)
+    user_super_login = deepcopy(user_super)
+    user_owner_login = deepcopy(user_owner)
 
-    def test_add(self):
+    def test_add_super(self):
         self.user_super_login['role'] = SUPER
         self.user_super_login['password'] = generate_password_hash(self.user_super_login['password'])
         user = UserModel(**self.user_super_login)
         user.save()
 
+    def test_add_owner(self):
+        self.user_owner_login['role'] = OWNER
+        self.user_owner_login['password'] = generate_password_hash(self.user_owner_login['password'])
+        user = UserModel(**self.user_owner_login)
+        user.save()
+
+
+#: 2 登录 super
+@pytest.mark.usefixtures('db')
+class TestApiPassport:
+    """api role testing"""
+    uri_prefix = '/api/passport'
+
+    user_id = {}
+
+    user_data = deepcopy(user_super)
+
+    def test_base_fetch(self):
+        u = UserModel.get_by_id(1)
+
+    def test_login_super(self, user, testapp, client, db):
+        """create successful."""
+
+        resp = client.post('%s/login' % (self.uri_prefix), data=self.user_data)
+
+        response_success(resp)
+
+        del self.user_data['password']
+        compare_req_resp(self.user_data, resp)
+
+
+#: 3 创建 space, users
+@pytest.mark.usefixtures('db')
+class TestApiSpaceInit:
+    """api role testing"""
+    uri_prefix = '/api/space'
+
+    user_id = {}
+
+    #: user list (1, 2, 3)
+    space_data = {
+        'name': u'walle-web 2.0',
+        'user_id': 2,
+    }
+
+    def test_base_create_space(self, user, testapp, client, db):
+        """create successful."""
+        # 1.create project
+        resp = client.post('%s/' % (self.uri_prefix), data=self.space_data)
 
-# class TestSpace:
-#     user_data_login = deepcopy(user_data_login)
-#
-#     def test_add(self):
-#         self.user_data_login['password'] = generate_password_hash(user_data_login['password'])
-#         user = UserModel(**self.user_data_login)
-#         user.save()
+        response_success(resp)
+        # compare_req_resp(self.space_data, resp)
+        current_app.logger.info(resp_json(resp)['data'])
+        self.space_data['space_id'] = resp_json(resp)['data']['id']

+ 2 - 1
tests/test_00_login.py

@@ -9,6 +9,7 @@ from walle.model.user import UserModel
 from copy import deepcopy
 from test_00_base import user_data_login
 
+#: 4 登录 owner
 @pytest.mark.usefixtures('db')
 class TestApiPassport:
     """api role testing"""
@@ -20,7 +21,7 @@ class TestApiPassport:
 
 
     def test_fetch(self):
-        u = UserModel.get_by_id(1)
+        u = UserModel.get_by_id(2)
 
     def test_login(self, user, testapp, client, db):
         """create successful."""

+ 3 - 0
tests/test_01_api_environment.py

@@ -17,16 +17,19 @@ class TestApiEnv:
 
     env_data = {
         'env_name': u'测试环境',
+        'space_id': 1,
     }
 
     user_name_2 = u'Production'
 
     env_data_2 = {
         'env_name': u'Production',
+        'space_id': 1,
     }
 
     env_data_remove = {
         'env_name': u'environment_remove',
+        'space_id': 1,
     }
 
     def test_create(self, user, testapp, client, db):

+ 1 - 1
tests/test_03_api_user.py

@@ -104,7 +104,7 @@ class TestApiUser:
             'size': 1,
         }
         response = {
-            'count': 6,
+            'count': 7,
         }
         resp = client.get('%s/?%s' % (self.uri_prefix, urllib.urlencode(query)))
         response_success(resp)

+ 17 - 2
tests/test_05_api_space.py

@@ -3,14 +3,15 @@
 import pytest
 from flask import current_app
 from utils import *
-
-
+from test_00_base import space_base
+from copy import deepcopy
 @pytest.mark.usefixtures('db')
 class TestApiSpace:
     """api role testing"""
     uri_prefix = '/api/space'
 
     user_id = {}
+    space_default_base = deepcopy(space_base)
 
     #: user list (1, 2, 3)
     space_data = {
@@ -33,6 +34,20 @@ class TestApiSpace:
         'members': json.dumps([{"user_id": 1, "role": "MASTER"}, {"user_id": 3, "role": "DEVELOPER"}]),
     }
 
+    def test_setUp(self):
+        pass
+
+
+    # 初始化 space_id=1的用户列表
+    def test_get_update_default_space(self, user, testapp, client):
+        """Login successful."""
+        # 1.update
+        self.space_default_base['members'] = json.dumps([{"user_id": 2, "role": "MASTER"}, {"user_id": 3, "role": "DEVELOPER"}])
+        resp = client.put('%s/%d' % (self.uri_prefix, 1), data=self.space_default_base)
+
+        response_success(resp)
+        self.compare_member_req_resp(self.space_data, resp)
+
     def test_create(self, user, testapp, client, db):
 
         """create successful."""

+ 12 - 0
tests/test_07_api_project.py

@@ -41,6 +41,11 @@ class TestApiProject:
         {"user_id": 3, "role": "MASTER"},
         {"user_id": 2, "role": "DEVELOPER"}
     ]
+    project_data_members_error = [
+        {"user_id": 3, "role": "MASTER"},
+        {"user_id": 2, "role": "DEVELOPER"},
+        {"user_id": 4, "role": "DEVELOPER"},
+    ]
 
     # should be equal to project_data_2.name
     project_name_2 = u'walle-web'
@@ -189,6 +194,13 @@ class TestApiProject:
 
     def test_get_update_members(self, user, testapp, client):
         """Login successful."""
+        from walle.service.code import Code
+        # 1.1 create user group error
+        headers = {'content-type': 'application/json'}
+        resp = client.put('%s/%d/members' % (self.uri_prefix, self.project_data_2['id']), data=json.dumps(self.project_data_members_error), headers=headers)
+        current_app.logger.info(resp)
+
+        response_error(resp, Code.user_not_in_space)
 
         # 1.1 create user group
         headers = {'content-type': 'application/json'}

+ 12 - 15
tests/test_models.py

@@ -1,13 +1,9 @@
 # -*- coding: utf-8 -*-
 """Model unit tests."""
-import datetime as dt
 
 import pytest
 
-from walle.model.user import RoleModel
-from walle.model.user import UserModel
 from walle.model.environment import EnvironmentModel
-from .factories import UserFactory
 
 
 @pytest.mark.usefixtures('db')
@@ -24,21 +20,22 @@ class TestFoo:
         # retrieved = Foo.get_by_id(user.id)
         # assert retrieved == user
 
+
 class TestEnvironment:
     def test_add(self):
         env_new = EnvironmentModel()
-        env_id = env_new.add(env_name=u'开发环境')
+        env_id = env_new.add(env_name=u'开发环境', space_id=1)
 
-# class TestUser:
-#     """User tests."""
-#
-#     def test_get_by_id(self):
-#         """Get user by ID."""
-#         user = Foo(username='wushuiyongoooo', email='wushuiyong@mail.com')
-#         user.save()
-#
-#         retrieved = User.get_by_id(user.id)
-#         assert retrieved == user
+    # class TestUser:
+    #     """User tests."""
+    #
+    #     def test_get_by_id(self):
+    #         """Get user by ID."""
+    #         user = Foo(username='wushuiyongoooo', email='wushuiyong@mail.com')
+    #         user.save()
+    #
+    #         retrieved = User.get_by_id(user.id)
+    #         assert retrieved == user
 
     # def test_created_at_defaults_to_datetime(self):
     #     """Test creation date."""

+ 2 - 2
walle/api/access.py

@@ -11,8 +11,8 @@
 from flask import request
 
 from walle.api.api import SecurityResource
-from walle.model.user import MenuModel
-from walle.model.user import RoleModel
+from walle.model.menu import MenuModel
+from walle.model.role import RoleModel
 
 
 class AccessAPI(SecurityResource):

+ 4 - 2
walle/api/environment.py

@@ -51,7 +51,8 @@ class EnvironmentAPI(SecurityResource):
         ]
 
         env_model = EnvironmentModel()
-        env_list, count = env_model.list(page=page, size=size, kw=kw)
+
+        env_list, count = env_model.list(page=page, size=size, kw=kw, space_id=self.space_id)
         return self.list_json(list=env_list, count=count, table=table, enable_create=permission.enable_role(MASTER) and current_user.role != SUPER)
 
     def item(self, env_id):
@@ -80,7 +81,8 @@ class EnvironmentAPI(SecurityResource):
         form = EnvironmentForm(request.form, csrf_enabled=False)
         if form.validate_on_submit():
             env_new = EnvironmentModel()
-            env_id = env_new.add(env_name=form.env_name.data)
+            # TODO space_id
+            env_id = env_new.add(env_name=form.env_name.data, space_id=self.space_id)
             if not env_id:
                 return self.render_json(code=-1)
             return self.render_json(data=env_new.item())

+ 1 - 1
walle/api/general.py

@@ -13,7 +13,7 @@ from flask import request, abort, session, current_app
 from flask_login import current_user, login_required
 from walle.api.api import SecurityResource
 from walle.model.record import RecordModel
-from walle.model.user import MenuModel
+from walle.model.menu import MenuModel
 from walle.model.user import UserModel
 from walle.service import emails
 from walle.service.deployer import Deployer

+ 3 - 26
walle/api/group.py

@@ -10,8 +10,9 @@
 from flask_login import login_required, current_user
 from flask import request
 from walle.form.group import GroupForm
-from walle.model.user import MemberModel, UserModel
-from walle.model.user import SpaceModel
+from walle.model.member import MemberModel
+from walle.model.user import UserModel
+from walle.model.space import SpaceModel
 from walle.model.tag import TagModel
 from walle.api.api import SecurityResource
 from flask import current_app
@@ -91,30 +92,6 @@ class GroupAPI(SecurityResource):
         group_info['group_id'] = group_info['id']
         return self.render_json(data=group_info)
 
-    def post(self):
-        """
-        create group
-        /group/
-
-        :return:
-        """
-        super(GroupAPI, self).post()
-        current_app.logger.info(request.form)
-        current_app.logger.info(request.form.user_ids)
-
-        form = GroupForm(request.form, csrf_enabled=False)
-        if form.validate_on_submit():
-            # user_ids = [int(uid) for uid in form.user_ids.data.split(',')]
-
-            group_id = 0
-            # group_new = MemberModel()
-            # group_id = group_new.add(group_name=form.group_name.data, user_ids=user_ids)
-            if not group_id:
-                return self.render_json(code=-1)
-            return self.render_json(data=group_new.item())
-        else:
-            return self.render_json(code=-1, message=form.errors)
-
     def put(self, group_id):
         """
         update group

+ 1 - 1
walle/api/project.py

@@ -15,7 +15,7 @@ from flask_login import login_required
 from walle.api.api import SecurityResource
 from walle.form.project import ProjectForm
 from walle.model.project import ProjectModel
-from walle.model.user import MemberModel
+from walle.model.member import MemberModel
 from walle.service.rbac.role import *
 from walle.service.extensions import permission
 

+ 1 - 1
walle/api/role.py

@@ -10,7 +10,7 @@
 
 from flask import request
 from walle.api.api import SecurityResource
-from walle.model.user import RoleModel
+from walle.model.role import RoleModel
 
 
 class RoleAPI(SecurityResource):

+ 10 - 4
walle/api/space.py

@@ -13,7 +13,9 @@ import json
 from flask import request, abort
 from walle.api.api import SecurityResource
 from walle.form.space import SpaceForm
-from walle.model.user import SpaceModel, MemberModel, UserModel
+from walle.model.space import SpaceModel
+from walle.model.member import MemberModel
+from walle.model.user import UserModel
 from walle.service.extensions import permission
 from walle.service.rbac.role import *
 
@@ -106,12 +108,14 @@ class SpaceAPI(SecurityResource):
 
         if action and action == 'switch':
             return self.switch(space_id)
-
+        current_app.logger.info(json.loads(request.form['members']))
         form = SpaceForm(request.form, csrf_enabled=False)
         form.set_id(space_id)
         if form.validate_on_submit():
             space = SpaceModel().get_by_id(space_id)
             data = form.form2dict()
+            current_app.logger.info(data)
+
             # a new type to update a model
             ret = space.update(data)
             # create group
@@ -146,6 +150,8 @@ class SpaceAPI(SecurityResource):
 
     def members(self, space_id):
         page = int(request.args.get('page', 1))
+        page = page - 1 if page else 0
         size = int(request.args.get('size', 10))
-        members = MemberModel(group_id=space_id).members(page=page, size=size)
-        return self.list_json(list=members['members'], count=members['count'], enable_create=permission.enable_role(OWNER))
+        kw = request.values.get('kw', '')
+        members, count = MemberModel(group_id=space_id).members_new(page=page, size=size, kw=kw)
+        return self.list_json(list=members, count=count, enable_create=permission.enable_role(OWNER))

+ 1 - 1
walle/api/user.py

@@ -13,7 +13,7 @@ from flask_login import current_user
 from walle.api.api import SecurityResource
 from walle.form.user import UserUpdateForm, RegistrationForm
 from walle.model.database import db
-from walle.model.user import MemberModel
+from walle.model.member import MemberModel
 from walle.model.user import UserModel
 from werkzeug.security import generate_password_hash
 from walle.service.rbac.role import *

+ 2 - 1
walle/form/space.py

@@ -14,7 +14,7 @@ from flask_wtf import Form
 from wtforms import TextField
 from wtforms import validators, ValidationError
 
-from walle.model.user import SpaceModel
+from walle.model.space import SpaceModel
 
 
 class SpaceForm(Form):
@@ -37,5 +37,6 @@ class SpaceForm(Form):
             'name': self.name.data if self.name.data else '',
             # TODO g.uid
             'user_id': self.user_id.data if self.user_id.data else '',
+            # TODO default value
             'status': 1,
         }

+ 0 - 1
walle/form/user.py

@@ -15,7 +15,6 @@ from wtforms import PasswordField, TextField
 from wtforms import validators, ValidationError
 from wtforms.validators import Regexp
 
-from walle.model.user import RoleModel
 from walle.model.user import UserModel
 from flask import current_app
 import re

+ 20 - 5
walle/model/environment.py

@@ -12,6 +12,7 @@ from sqlalchemy import String, Integer, DateTime
 from walle.model.database import db, Model
 from walle.service.extensions import permission
 from walle.service.rbac.role import *
+from walle import model
 
 
 # 环境级别
@@ -26,11 +27,12 @@ class EnvironmentModel(Model):
     # 表的结构:
     id = db.Column(Integer, primary_key=True, autoincrement=True)
     name = db.Column(String(20))
+    space_id = db.Column(Integer)
     status = db.Column(Integer)
     created_at = db.Column(DateTime, default=current_time)
     updated_at = db.Column(DateTime, default=current_time, onupdate=current_time)
 
-    def list(self, page=0, size=10, kw=None):
+    def list(self, page=0, size=10, kw=None, space_id=None):
         """
         获取分页列表
         :param page:
@@ -41,10 +43,22 @@ class EnvironmentModel(Model):
         query = self.query.filter(EnvironmentModel.status.notin_([self.status_remove]))
         if kw:
             query = query.filter(EnvironmentModel.name.like('%' + kw + '%'))
-        count = query.count()
+        if space_id:
+            query = query.filter(EnvironmentModel.space_id==space_id)
 
+        SpaceModel = model.space.SpaceModel
+        query = query.join(SpaceModel, SpaceModel.id==EnvironmentModel.space_id)
+        query = query.add_columns(SpaceModel.name)
+        count = query.count()
         data = query.order_by(EnvironmentModel.id.desc()).offset(int(size) * int(page)).limit(size).all()
-        env_list = [p.to_json() for p in data]
+
+        current_app.logger.info(data)
+        env_list = []
+        for p in data:
+            item = p[0].to_json()
+            item['space_name'] = p[1]
+            env_list.append(item)
+
         return env_list, count
 
     def item(self, env_id=None):
@@ -56,9 +70,9 @@ class EnvironmentModel(Model):
         data = self.query.filter(EnvironmentModel.status.notin_([self.status_remove])).filter_by(id=self.id).first()
         return data.to_json() if data else []
 
-    def add(self, env_name):
+    def add(self, env_name, space_id):
         # todo permission_ids need to be formated and checked
-        env = EnvironmentModel(name=env_name, status=self.status_open)
+        env = EnvironmentModel(name=env_name, status=self.status_open, space_id=space_id)
 
         db.session.add(env)
         db.session.commit()
@@ -92,6 +106,7 @@ class EnvironmentModel(Model):
         item = {
             'id': self.id,
             'status': self.status,
+            'space_id': self.space_id,
             'env_name': self.name,
             'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
             'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S'),

+ 291 - 0
walle/model/member.py

@@ -0,0 +1,291 @@
+# -*- coding: utf-8 -*-
+"""
+    walle-web
+
+    :copyright: © 2015-2019 walle-web.io
+    :created time: 2018-11-26 16:29:58
+    :author: wushuiyong@walle-web.io
+"""
+from datetime import datetime
+
+from sqlalchemy import String, Integer, DateTime
+from walle import model
+from walle.model.database import SurrogatePK
+from walle.model.database import db, Model
+from walle.model.user import UserModel
+from walle.service.rbac.role import *
+
+# 项目配置表
+class MemberModel(SurrogatePK, Model):
+    __tablename__ = 'members'
+
+    current_time = datetime.now()
+    group_id = None
+    project_id = None
+
+    source_type_project = 'project'
+    source_type_group = 'group'
+
+    # 表的结构:
+    id = db.Column(Integer, primary_key=True, autoincrement=True)
+    user_id = db.Column(Integer, db.ForeignKey('users.id'))
+    source_id = db.Column(Integer)
+    source_type = db.Column(String(10))
+    access_level = db.Column(String(10))
+    status = db.Column(Integer)
+    created_at = db.Column(DateTime, default=current_time)
+    updated_at = db.Column(DateTime, default=current_time, onupdate=current_time)
+    group_name = None
+
+    # TODO group id全局化
+
+    def spaces(self, user_id=None):
+        """
+        获取分页列表
+        :param page:
+        :param size:
+        :return:
+        """
+        SpaceModel = model.space.SpaceModel
+        filters = {
+            MemberModel.status.notin_([self.status_remove]),
+            MemberModel.source_type == self.source_type_group
+        }
+        query = self.query.filter(*filters).with_labels().with_entities(MemberModel.source_id, MemberModel.access_level,
+                                                                        SpaceModel.name)
+        if user_id:
+            query = query.filter_by(user_id=user_id)
+
+        query = query.join(SpaceModel, SpaceModel.id == MemberModel.source_id)
+
+        spaces = query.all()
+        current_app.logger.info(spaces)
+        return {space[0]: {'id': space[0], 'role': space[1], 'name': space[2]} for space in spaces}
+
+    def projects(self, user_id=None, space_id=None):
+        """
+        获取分页列表
+        :param page:
+        :param size:
+        :return:
+        """
+        filters = {
+            MemberModel.status.notin_([self.status_remove]),
+            MemberModel.source_type == self.source_type_project
+        }
+        query = self.query.filter(*filters)
+        if user_id:
+            query = query.filter_by(user_id=user_id)
+
+        # if project_id:
+        #     query = query.filter_by(source_id=project_id)
+
+        projects = query.all()
+        current_app.logger.info(projects)
+
+        return projects
+
+
+    def update_group(self, members, group_name=None):
+        SpaceModel = model.space.SpaceModel
+
+        # 修复空间名称
+        if group_name:
+            SpaceModel(id=self.group_id).update({'name': group_name})
+        # # 修改tag信息
+        # if group_name:
+        #     tag_model = TagModel.query.filter_by(label='user_group').filter_by(id=self.group_id).first()
+        #     if tag_model.name != group_name:
+        #         tag_model.name = group_name
+
+        # 修改用户组成员
+        # clean up
+        filters = {
+            MemberModel.source_id == self.group_id,
+            MemberModel.source_type == self.source_type_group,
+        }
+        MemberModel.query.filter(*filters).delete()
+
+        current_app.logger.info(members)
+        # insert all
+        for member in members:
+            current_app.logger.info(member)
+            current_app.logger.info(member['role'])
+            update = {
+                'user_id': member['user_id'],
+                'source_id': self.group_id,
+                'source_type': self.source_type_group,
+                'access_level': member['role'].upper(),
+                'status': self.status_available,
+            }
+            m = MemberModel(**update)
+            db.session.add(m)
+
+        ret = db.session.commit()
+
+        return ret
+
+    def update_project(self, project_id, members, group_name=None):
+        space_info = model.project.ProjectModel.query.filter_by(id=project_id).first().to_json()
+        group_model = self.members(group_id=space_info['space_id'])
+        user_update = []
+
+        for member in members:
+            user_update.append(member['user_id'])
+
+        current_app.logger.info(group_model['user_ids'])
+        current_app.logger.info(user_update)
+
+        # project新增用户是否在space's group中,无则抛出
+        if list(set(user_update).difference(set(group_model['user_ids']))):
+            raise WalleError(Code.user_not_in_space)
+
+        # 修改用户组成员
+        # clean up
+        filters = {
+            MemberModel.source_id == project_id,
+            MemberModel.source_type == self.source_type_project,
+        }
+        MemberModel.query.filter(*filters).delete()
+
+        # insert all
+        for member in members:
+            insert = {
+                'user_id': member['user_id'],
+                'source_id': project_id,
+                'source_type': self.source_type_project,
+                'access_level': member['role'].upper(),
+                'status': self.status_available,
+            }
+            group = MemberModel(**insert)
+            db.session.add(group)
+
+        ret = db.session.commit()
+
+        return ret
+
+    def members(self, group_id=None, project_id=None, page=1, size=10, kw=None):
+        """
+        获取单条记录
+        :param role_id:
+        :return:
+        """
+        group_id = group_id if group_id else self.group_id
+        project_id = project_id if project_id else self.project_id
+        source_id = group_id if group_id else project_id
+        source_type = self.source_type_group if group_id else self.source_type_project
+        filters = {
+            'status': {'nin': [self.status_remove]},
+            'source_id': {'=': source_id},
+            'source_type': {'=': source_type},
+        }
+        # if kw:
+        #     filters['email'] = {'like': kw}
+
+        # TODO
+        groups, count = MemberModel.query_paginate(page=page, limit=size, filter_name_dict=filters)
+
+        user_ids = []
+        user_role = members = {}
+        current_app.logger.info(groups)
+
+        for group_info in groups:
+            user_ids.append(group_info.user_id)
+            # TODO
+            user_role[group_info.user_id] = group_info.access_level
+
+        current_app.logger.info(user_ids)
+        user_model = UserModel()
+        user_info = user_model.fetch_by_uid(uids=set(user_ids))
+        if user_info:
+            for user in user_info:
+                if user_role.has_key(user['id']):
+                    user['role'] = user_role[user['id']]
+
+        members['user_ids'] = user_ids
+        members['members'] = user_info
+        members['count'] = count
+        return members
+
+    def members_new(self, group_id=None, project_id=None, page=1, size=10, kw=None):
+        """
+        获取单条记录
+        :param role_id:
+        :return:
+        """
+        group_id = group_id if group_id else self.group_id
+        project_id = project_id if project_id else self.project_id
+        source_id = group_id if group_id else project_id
+        source_type = self.source_type_group if group_id else self.source_type_project
+        query = self.query.filter(MemberModel.source_id == source_id).filter(MemberModel.source_type == source_type)
+        query = query.join(UserModel, UserModel.id == MemberModel.user_id)
+        if kw:
+            query = query.filter(or_(UserModel.username.like('%' + kw + '%'), UserModel.email.like('%' + kw + '%')))
+
+        query = query.add_columns(UserModel.username)
+
+        count = query.count()
+        data = query.order_by(MemberModel.id.asc()).offset(int(size) * int(page)).limit(size).all()
+
+        list = []
+        for p in data:
+            item = p[0].to_json()
+            item['username'] = p[1]
+            list.append(item)
+
+        return list, count
+
+        # TODO
+        groups, count = MemberModel.query_paginate(page=page, limit=size, filter_name_dict=filters)
+
+        user_ids = []
+        user_role = members = {}
+        current_app.logger.info(groups)
+
+        for group_info in groups:
+            user_ids.append(group_info.user_id)
+            # TODO
+            user_role[group_info.user_id] = group_info.access_level
+
+        current_app.logger.info(user_ids)
+        user_model = UserModel()
+        user_info = user_model.fetch_by_uid(uids=set(user_ids))
+        if user_info:
+            for user in user_info:
+                if user_role.has_key(user['id']):
+                    user['role'] = user_role[user['id']]
+
+        members['user_ids'] = user_ids
+        members['members'] = user_info
+        members['count'] = count
+        return members
+
+    def remove(self, group_id=None, user_id=None, project_id=None):
+        """
+
+        :param role_id:
+        :return:
+        """
+        if group_id:
+            MemberModel.query.filter_by(group_id=group_id).update({'status': self.status_remove})
+        elif user_id:
+            MemberModel.query.filter_by(user_id=user_id).update({'status': self.status_remove})
+        elif self.group_id:
+            MemberModel.query.filter_by(group_id=self.group_id).update({'status': self.status_remove})
+        elif project_id:
+            MemberModel.query.filter_by(project_id=project_id).update({'status': self.status_remove})
+
+        ret = db.session.commit()
+
+        return ret
+
+    def to_json(self):
+        return {
+            'id': self.id,
+            'user_id': self.user_id,
+            'group_id': self.group_id,
+            'group_name': self.group_name,
+            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
+            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S'),
+        }
+

+ 88 - 0
walle/model/menu.py

@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+"""
+    walle-web
+
+    :copyright: © 2015-2019 walle-web.io
+    :created time: 2018-11-26 16:30:44
+    :author: wushuiyong@walle-web.io
+"""
+
+from datetime import datetime
+
+from sqlalchemy import String, Integer, DateTime
+from walle import model
+from walle.model.database import SurrogatePK
+from walle.model.database import db, Model
+
+
+class MenuModel(SurrogatePK, Model):
+    __tablename__ = 'menus'
+
+    type_module = 'module'
+    type_controller = 'controller'
+    type_action = 'action'
+
+    status_open = 1
+    status_close = 2
+    current_time = datetime.now()
+
+    # 表的结构:
+    id = db.Column(Integer, primary_key=True, autoincrement=True)
+    name_cn = db.Column(String(30))
+    name_en = db.Column(String(30))
+    pid = db.Column(Integer)
+    type = db.Column(String(30))
+    sequence = db.Column(Integer)
+    archive = db.Column(Integer)
+    icon = db.Column(String(30))
+    url = db.Column(String(30))
+    visible = db.Column(Integer)
+    role = db.Column(Integer)
+    created_at = db.Column(DateTime, default=current_time)
+    updated_at = db.Column(DateTime, default=current_time, onupdate=current_time)
+
+    def menu(self, role):
+        RoleModel = model.role.RoleModel
+        data = {}
+        filters = {
+            MenuModel.visible == 1,
+            MenuModel.role >= role
+        }
+        query = self.query \
+            .filter(*filters) \
+            .order_by('sequence asc') \
+            .all()
+        for item in query:
+            if item.type == self.type_module:
+                module = {
+                    'title': item.name_cn,
+                    'icon': item.icon,
+                    'sub_menu': [],
+                }
+                if item.url:
+                    module['url'] = RoleModel.menu_url(item.url)
+                data[item.id] = module
+            elif item.type == self.type_controller:
+                data[item.pid]['sub_menu'].append({
+                    'title': item.name_cn,
+                    'icon': item.icon,
+                    'url': RoleModel.menu_url(item.url),
+                })
+
+        return data.values()
+
+    def to_json(self):
+        return {
+            'id': self.id,
+            'name_cn': self.name_cn,
+            'name_en': self.name_en,
+            'pid': self.pid,
+            'type': self.type,
+            'sequence': self.sequence,
+            'archive': self.archive,
+            'icon': self.icon,
+            'url': self.url,
+            'visible': self.visible,
+            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
+            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S'),
+        }

+ 1 - 1
walle/model/project.py

@@ -74,7 +74,7 @@ class ProjectModel(SurrogatePK, Model):
         query = query.filter(EnvironmentModel.status.notin_([self.status_remove]))
 
         # 关联 spaces
-        SpaceModel = model.user.SpaceModel
+        SpaceModel = model.space.SpaceModel
         query = query.join(SpaceModel, SpaceModel.id == ProjectModel.space_id)
         query = query.filter(SpaceModel.status.notin_([self.status_remove]))
 

+ 53 - 0
walle/model/role.py

@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+"""
+    walle-web
+
+    :copyright: © 2015-2019 walle-web.io
+    :created time: 2018-11-26 16:06:44
+    :author: wushuiyong@walle-web.io
+"""
+
+from datetime import datetime
+
+from sqlalchemy import String, Integer, DateTime
+from walle.model.database import SurrogatePK
+from walle.model.database import db, Model
+from walle.model.user import UserModel
+from walle.service.extensions import permission
+from walle.service.rbac.role import *
+
+
+class RoleModel(object):
+    _role_super = 'SUPER'
+
+    _role_owner = 'OWNER'
+
+    _role_master = 'MASTER'
+
+    _role_developer = 'DEVELOPER'
+
+    _role_reporter = 'REPORTER'
+
+    @classmethod
+    def list(cls):
+        roles = [
+            {'id': cls._role_super, 'name': '超级管理员'},
+            {'id': cls._role_owner, 'name': '空间所有者'},
+            {'id': cls._role_master, 'name': '项目管理员'},
+            {'id': cls._role_developer, 'name': '开发者'},
+            {'id': cls._role_reporter, 'name': '访客'},
+        ]
+        return roles, len(roles)
+
+    @classmethod
+    def item(cls, role_id):
+        return None
+
+    @classmethod
+    def menu_url(cls, url):
+        if url == '/':
+            return url
+        prefix = 'admin' if current_user.role == SUPER else session['space_info']['name']
+
+        return '/' + prefix + url
+

+ 132 - 0
walle/model/space.py

@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+"""
+    walle-web
+
+    :copyright: © 2015-2019 walle-web.io
+    :created time: 2018-11-25 16:01:41
+    :author: wushuiyong@walle-web.io
+"""
+from datetime import datetime
+
+from sqlalchemy import String, Integer, DateTime
+from walle.model.database import SurrogatePK, db, Model
+from walle.model.user import UserModel
+from walle.service.extensions import permission
+from walle.service.rbac.role import *
+from walle import model
+
+# 项目配置表
+class SpaceModel(SurrogatePK, Model):
+    # 表的名字:
+    __tablename__ = 'spaces'
+    current_time = datetime.now()
+    status_close = 0
+    status_open = 1
+
+    # 表的结构:
+    id = db.Column(Integer, primary_key=True, autoincrement=True)
+    user_id = db.Column(Integer)
+    name = db.Column(String(100))
+    status = db.Column(Integer)
+
+    created_at = db.Column(DateTime, default=current_time)
+    updated_at = db.Column(DateTime, default=current_time, onupdate=current_time)
+
+    def list(self, page=0, size=10, kw=None):
+        """
+        获取分页列表
+        :param page:
+        :param size:
+        :return:
+        """
+        query = self.query.filter(SpaceModel.status.notin_([self.status_remove]))
+        if kw:
+            query = query.filter(SpaceModel.name.like('%' + kw + '%'))
+
+        # TODO 如果是超管,可以全量,否则需要过滤自己有权限的空间列表
+        if current_user.role <> SUPER:
+            query = query.filter_by(user_id=current_user.id)
+        count = query.count()
+        data = query.order_by(SpaceModel.id.desc()).offset(int(size) * int(page)).limit(size).all()
+
+        uid2name = UserModel.uid2name(data=data)
+        list = [p.to_json(uid2name) for p in data]
+        return list, count
+
+    def item(self, id=None):
+        """
+        获取单条记录
+        :param role_id:
+        :return:
+        """
+        MemberModel = model.member.MemberModel
+        id = id if id else self.id
+        data = self.query.filter_by(id=id).first()
+        members = MemberModel(group_id=id).members()
+
+        if not data:
+            return []
+
+        data = data.to_json()
+
+        return dict(data, **members)
+
+    def add(self, *args, **kwargs):
+        # todo permission_ids need to be formated and checked
+        data = dict(*args)
+
+        # tag = TagModel(name=data['name'], label='user_group')
+        # db.session.add(tag)
+        # db.session.commit()
+        data = dict(*args)
+        space = SpaceModel(**data)
+        db.session.add(space)
+        db.session.commit()
+
+        self.id = space.id
+        return self.id
+
+    def update(self, *args, **kwargs):
+        # todo permission_ids need to be formated and checked
+        # a new type to update a model
+
+        update_data = dict(*args)
+        return super(SpaceModel, self).update(**update_data)
+
+    def remove(self, space_id=None):
+        """
+
+        :param space_id:
+        :return:
+        """
+        space_id = space_id if space_id else self.id
+        SpaceModel.query.filter_by(id=space_id).update({'status': self.status_remove})
+
+        ret = db.session.commit()
+
+        return ret
+
+    def to_json(self, uid2name=None):
+        item = {
+            'id': self.id,
+            'user_id': self.user_id,
+            'user_name': uid2name[self.user_id] if uid2name and uid2name.has_key(self.user_id) else '',
+            # TODO
+            'group_id': 'self.group_id',
+            'name': self.name,
+            'status': self.status,
+            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
+            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S'),
+        }
+        item.update(self.enable())
+        return item
+
+    def enable(self):
+        return {
+            'enable_update': permission.enable_uid(self.user_id) or permission.enable_role(OWNER),
+            'enable_delete': permission.enable_uid(self.user_id) or permission.enable_role(OWNER),
+            'enable_create': False,
+            'enable_online': False,
+            'enable_audit': False,
+            'enable_block': permission.enable_role(MASTER),
+        }

+ 8 - 635
walle/model/user.py

@@ -4,24 +4,18 @@
 # @Created Time : 日  1/ 1 23:43:12 2017
 # @Description:
 
+from datetime import datetime
+
 from flask_login import UserMixin
 from sqlalchemy import String, Integer, DateTime, or_
-from werkzeug.security import check_password_hash, generate_password_hash
-
-# from flask_cache import Cache
-from datetime import datetime
-from walle.service.extensions import login_manager
-from walle.model.database import SurrogatePK, db, Model
-from walle.model.tag import TagModel
 from sqlalchemy.orm import aliased
+from walle.model.database import SurrogatePK, db, Model
+from walle.service.extensions import permission
 from walle.service.rbac.access import Access as AccessRbac
-from flask import current_app, session, abort
 from walle.service.rbac.role import *
-from walle.service.error import WalleError
-from flask_login import current_user as g
-from walle.service.extensions import permission
+from walle import model
+from werkzeug.security import check_password_hash, generate_password_hash
 
-import walle.model
 
 class UserModel(UserMixin, SurrogatePK, Model):
     # 表的名字:
@@ -52,90 +46,7 @@ class UserModel(UserMixin, SurrogatePK, Model):
         2: '冻结',
     }
 
-    '''
-    current_user 基础方法
-      "__abstract__",
-      "__class__",
-      "__delattr__",
-      "__dict__",
-      "__doc__",
-      "__eq__",
-      "__format__",
-      "__getattribute__",
-      "__hash__",
-      "__init__",
-      "__mapper__",
-      "__module__",
-      "__ne__",
-      "__new__",
-      "__reduce__",
-      "__reduce_ex__",
-      "__repr__",
-      "__setattr__",
-      "__sizeof__",
-      "__str__",
-      "__subclasshook__",
-      "__table__",
-      "__table_args__",
-      "__tablename__",
-      "__weakref__",
-      "_cached_tablename",
-      "_decl_class_registry",
-      "_sa_class_manager",
-      "_sa_instance_state",
-      "avatar",
-      "avatar_url",
-      "block_active",
-      "column_name_set",
-      "create",
-      "create_from_dict",
-      "create_or_update",
-      "created_at",
-      "current_time",
-      "delete",
-      "dump_schema",
-      "email",
-      "enable",
-      "fetch_access_list_by_role_id",
-      "fetch_by_uid",
-      "general_password",
-      "get_by_id",
-      "get_common_fields",
-      "get_id",
-      "id",
-      "is_active",
-      "is_anonymous",
-      "is_authenticated",
-      "is_email_verified",
-      "item",
-      "list",
-      "metadata",
-      "password",
-      "password_hash",
-      "query",
-      "query_class",
-      "query_paginate",
-      "query_paginate_and_dump_schema",
-      "remove",
-      "save",
-      "set_password",
-      "status",
-      "status_active",
-      "status_available",
-      "status_blocked",
-      "status_default",
-      "status_mapping",
-      "status_remove",
-      "to_dict",
-      "to_json",
-      "uid2name",
-      "update",
-      "update_avatar",
-      "update_name_pwd",
-      "updated_at",
-      "username",
-      "verify_password"
-    '''
+
     def add(self, *args, **kwargs):
         data = dict(*args)
         user = UserModel(**data)
@@ -153,7 +64,6 @@ class UserModel(UserMixin, SurrogatePK, Model):
         data = self.query.filter_by(id=self.id).filter(UserModel.status.notin_([self.status_remove])).first()
         return data.to_json() if data else []
 
-
     def update(self, *args, **kwargs):
         # todo permission_ids need to be formated and checked
         # a new type to update a model
@@ -217,24 +127,6 @@ class UserModel(UserMixin, SurrogatePK, Model):
         self.password = generate_password_hash(password)
         return generate_password_hash(password)
 
-    def fetch_access_list_by_role_id(self, role_id):
-        module = aliased(MenuModel)
-        controller = aliased(MenuModel)
-        action = aliased(MenuModel)
-        role = RoleModel.query.get(role_id)
-        access_ids = role.access_ids.split(',')
-
-        data = db.session \
-            .query(controller.name_en, controller.name_cn,
-                   action.name_en, action.name_cn) \
-            .outerjoin(action, action.pid == controller.id) \
-            .filter(module.type == MenuModel.type_module) \
-            .filter(controller.id.in_(access_ids)) \
-            .filter(action.id.in_(access_ids)) \
-            .all()
-
-        return [AccessRbac.resource(a_en, c_en) for c_en, c_cn, a_en, a_cn in data if c_en and a_en]
-
     def is_authenticated(self):
         return True
 
@@ -269,6 +161,7 @@ class UserModel(UserMixin, SurrogatePK, Model):
         return user_list, count
 
     def has_spaces(self):
+        MemberModel = model.member.MemberModel
         return MemberModel().spaces(user_id=self.id)
 
     @classmethod
@@ -302,7 +195,6 @@ class UserModel(UserMixin, SurrogatePK, Model):
 
         current_app.logger.info('============ SecurityResource.__init__ ============')
 
-
     @classmethod
     def avatar_url(cls, avatar):
         avatar = avatar if avatar else 'default.jpg'
@@ -369,522 +261,3 @@ class UserModel(UserMixin, SurrogatePK, Model):
             'enable_block': False,
         }
 
-class MenuModel(SurrogatePK, Model):
-    __tablename__ = 'menus'
-
-    type_module = 'module'
-    type_controller = 'controller'
-    type_action = 'action'
-
-    status_open = 1
-    status_close = 2
-    current_time = datetime.now()
-
-    # 表的结构:
-    id = db.Column(Integer, primary_key=True, autoincrement=True)
-    name_cn = db.Column(String(30))
-    name_en = db.Column(String(30))
-    pid = db.Column(Integer)
-    type = db.Column(String(30))
-    sequence = db.Column(Integer)
-    archive = db.Column(Integer)
-    icon = db.Column(String(30))
-    url = db.Column(String(30))
-    visible = db.Column(Integer)
-    role = db.Column(Integer)
-    created_at = db.Column(DateTime, default=current_time)
-    updated_at = db.Column(DateTime, default=current_time, onupdate=current_time)
-
-    def menu(self, role):
-        data = {}
-        filters = {
-            MenuModel.visible == 1,
-            MenuModel.role >= role
-        }
-        query = self.query \
-            .filter(*filters) \
-            .order_by('sequence asc') \
-            .all()
-        for item in query:
-            if item.type == self.type_module:
-                module = {
-                    'title': item.name_cn,
-                    'icon': item.icon,
-                    'sub_menu': [],
-                }
-                if item.url:
-                    module['url'] = RoleModel.menu_url(item.url)
-                data[item.id] = module
-            elif item.type == self.type_controller:
-                data[item.pid]['sub_menu'].append({
-                    'title': item.name_cn,
-                    'icon': item.icon,
-                    'url': RoleModel.menu_url(item.url),
-                })
-
-        return data.values()
-
-    def list(self):
-        """
-        获取分页列表
-        :param page:
-        :param size:
-        :param kw:
-        :return:
-        """
-        menus_module = {}
-        menus_controller = {}
-        module = aliased(MenuModel)
-        controller = aliased(MenuModel)
-        action = aliased(MenuModel)
-
-        data = db.session.query(module.id, module.name_cn, controller.id, controller.name_cn, action.id, action.name_cn) \
-            .outerjoin(controller, controller.pid == module.id) \
-            .outerjoin(action, action.pid == controller.id) \
-            .filter(module.type == self.type_module) \
-            .all()
-        for m_id, m_name, c_id, c_name, a_id, a_name in data:
-            # module
-            if m_id not in menus_module:
-                menus_module[m_id] = {
-                    'id': m_id,
-                    'title': m_name,
-                    'sub_menu': {},
-                }
-            # controller
-            if c_id not in menus_module[m_id]['sub_menu'] and c_name:
-                menus_module[m_id]['sub_menu'][c_id] = {
-                    'id': c_id,
-                    'title': c_name,
-                    'sub_menu': {},
-                }
-            # action
-            if c_id not in menus_controller:
-                menus_controller[c_id] = []
-            if a_name:
-                menus_controller[c_id].append({
-                    'id': a_id,
-                    'title': a_name,
-                })
-        menus = []
-        for m_id, m_info in menus_module.items():
-            for c_id, c_info in m_info['sub_menu'].items():
-                m_info['sub_menu'][c_id]['sub_menu'] = menus_controller[c_id]
-            menus.append({
-                'id': m_id,
-                'title': m_info['title'],
-                'sub_menu': m_info['sub_menu'].values(),
-            })
-
-        return menus
-
-    def to_json(self):
-        return {
-            'id': self.id,
-            'name_cn': self.name_cn,
-            'name_en': self.name_en,
-            'pid': self.pid,
-            'type': self.type,
-            'sequence': self.sequence,
-            'archive': self.archive,
-            'icon': self.icon,
-            'url': self.url,
-            'visible': self.visible,
-            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
-            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S'),
-        }
-
-
-class RoleModel(object):
-    _role_super = 'SUPER'
-
-    _role_owner = 'OWNER'
-
-    _role_master = 'MASTER'
-
-    _role_developer = 'DEVELOPER'
-
-    _role_reporter = 'REPORTER'
-
-    @classmethod
-    def list(cls):
-        roles = [
-            {'id': cls._role_super, 'name': '超级管理员'},
-            {'id': cls._role_owner, 'name': '空间所有者'},
-            {'id': cls._role_master, 'name': '项目管理员'},
-            {'id': cls._role_developer, 'name': '开发者'},
-            {'id': cls._role_reporter, 'name': '访客'},
-        ]
-        return roles, len(roles)
-
-    @classmethod
-    def item(cls, role_id):
-        return None
-
-    @classmethod
-    def menu_url(cls, url):
-        if url == '/':
-            return url
-        prefix = 'admin' if g.role == SUPER else session['space_info']['name']
-
-        return '/' + prefix + url
-
-
-# 项目配置表
-class MemberModel(SurrogatePK, Model):
-    __tablename__ = 'members'
-
-    current_time = datetime.now()
-    group_id = None
-    project_id = None
-
-    source_type_project = 'project'
-    source_type_group = 'group'
-
-    # 表的结构:
-    id = db.Column(Integer, primary_key=True, autoincrement=True)
-    user_id = db.Column(Integer, db.ForeignKey('users.id'))
-    source_id = db.Column(Integer)
-    source_type = db.Column(String(10))
-    access_level = db.Column(String(10))
-    status = db.Column(Integer)
-    created_at = db.Column(DateTime, default=current_time)
-    updated_at = db.Column(DateTime, default=current_time, onupdate=current_time)
-    group_name = None
-
-    # TODO group id全局化
-
-    def spaces(self, user_id=None):
-        """
-        获取分页列表
-        :param page:
-        :param size:
-        :return:
-        """
-        filters = {
-            MemberModel.status.notin_([self.status_remove]),
-            MemberModel.source_type == self.source_type_group
-        }
-        query = self.query.filter(*filters).with_labels().with_entities(MemberModel.source_id, MemberModel.access_level, SpaceModel.name)
-        if user_id:
-            query = query.filter_by(user_id=user_id)
-
-        query = query.join(SpaceModel, SpaceModel.id==MemberModel.source_id)
-
-        spaces = query.all()
-        current_app.logger.info(spaces)
-        return {space[0]: {'id': space[0], 'role': space[1], 'name': space[2]} for space in spaces}
-
-
-    def projects(self, user_id=None, space_id=None):
-        """
-        获取分页列表
-        :param page:
-        :param size:
-        :return:
-        """
-        filters = {
-            MemberModel.status.notin_([self.status_remove]),
-            MemberModel.source_type == self.source_type_project
-        }
-        query = self.query.filter(*filters)
-        if user_id:
-            query = query.filter_by(user_id=user_id)
-
-        # if project_id:
-        #     query = query.filter_by(source_id=project_id)
-
-        projects = query.all()
-        current_app.logger.info(projects)
-
-        return projects
-
-        group, count = MemberModel.query_paginate(page=page, limit=size, filter_name_dict=filters)
-
-        list = [p.to_json() for p in group]
-        return list, count
-
-    def add(self, space_name, members):
-        """
-
-        :param space_name:
-        :param members: [{'user_id': 1, 'project_id': 2}]
-        :return:
-        """
-        tag = TagModel(name=space_name, label='user_group')
-        db.session.add(tag)
-        db.session.commit()
-
-
-        for member in members:
-            user_group = MemberModel(group_id=tag.id, user_id=member['user_id'], project_id=member['project_id'])
-            db.session.add(user_group)
-
-        db.session.commit()
-
-        if tag.id:
-            self.group_id = tag.id
-
-        return tag.id
-
-    def update_group(self, members, group_name=None):
-        # 修复空间名称
-        if group_name:
-            SpaceModel(id=self.group_id).update({'name': group_name})
-        # # 修改tag信息
-        # if group_name:
-        #     tag_model = TagModel.query.filter_by(label='user_group').filter_by(id=self.group_id).first()
-        #     if tag_model.name != group_name:
-        #         tag_model.name = group_name
-
-        # 修改用户组成员
-        # clean up
-        filters = {
-            MemberModel.source_id == self.group_id,
-            MemberModel.source_type == self.source_type_group,
-        }
-        MemberModel.query.filter(*filters).delete()
-
-        # insert all
-        for member in members:
-            update = {
-                'user_id': member['user_id'],
-                'source_id': self.group_id,
-                'source_type': self.source_type_group,
-                'access_level': member['role'].upper(),
-                'status': self.status_available,
-            }
-            m = MemberModel(**update)
-            db.session.add(m)
-
-
-        ret = db.session.commit()
-
-        return ret
-
-    def update_project(self, project_id, members, group_name=None):
-        space_info = walle.model.project.ProjectModel.query.filter_by(id=project_id).first().to_json()
-        group_model = self.members(group_id=space_info['space_id'])
-        user_update = []
-
-        for member in members:
-            user_update.append(member['user_id'])
-
-        current_app.logger.info(group_model['user_ids'])
-        current_app.logger.info(user_update)
-
-        # project新增用户是否在space's group中,无则抛出
-        if list(set(user_update).difference(set(group_model['user_ids']))):
-            raise WalleError(Code.user_not_in_space)
-
-        # 修改用户组成员
-        # clean up
-        filters = {
-            MemberModel.source_id == project_id,
-            MemberModel.source_type == self.source_type_project,
-        }
-        MemberModel.query.filter(*filters).delete()
-
-        # insert all
-        for member in members:
-            insert = {
-                'user_id': member['user_id'],
-                'source_id': project_id,
-                'source_type': self.source_type_project,
-                'access_level': member['role'].upper(),
-                'status': self.status_available,
-            }
-            group = MemberModel(**insert)
-            db.session.add(group)
-
-        ret = db.session.commit()
-
-        return ret
-
-    def members(self, group_id=None, project_id=None, page=1, size=10):
-        """
-        获取单条记录
-        :param role_id:
-        :return:
-        """
-        group_id = group_id if group_id else self.group_id
-        project_id = project_id if project_id else self.project_id
-        source_id = group_id if group_id else project_id
-        source_type = self.source_type_group if group_id else self.source_type_project
-        filters = {
-            'status': {'nin': [self.status_remove]},
-            'source_id': {'=': source_id},
-            'source_type': {'=': source_type},
-        }
-
-        # TODO
-        groups, count = MemberModel.query_paginate(page=page, limit=size, filter_name_dict=filters)
-
-        user_ids = []
-        user_role = members = {}
-        current_app.logger.info(groups)
-
-        for group_info in groups:
-            user_ids.append(group_info.user_id)
-            # TODO
-            user_role[group_info.user_id] = group_info.access_level
-
-        current_app.logger.info(user_ids)
-        user_model = UserModel()
-        user_info = user_model.fetch_by_uid(uids=set(user_ids))
-        if user_info:
-            for user in user_info:
-                if user['id'] in user_role:
-                    user['role'] = user_role[user['id']]
-
-        members['user_ids'] = user_ids
-        members['members'] = user_info
-        members['count'] = count
-        return members
-
-    def remove(self, group_id=None, user_id=None, project_id=None):
-        """
-
-        :param role_id:
-        :return:
-        """
-        if group_id:
-            MemberModel.query.filter_by(group_id=group_id).update({'status': self.status_remove})
-        elif user_id:
-            MemberModel.query.filter_by(user_id=user_id).update({'status': self.status_remove})
-        elif self.group_id:
-            MemberModel.query.filter_by(group_id=self.group_id).update({'status': self.status_remove})
-        elif project_id:
-            MemberModel.query.filter_by(project_id=project_id).update({'status': self.status_remove})
-
-        ret = db.session.commit()
-
-        return ret
-
-    def to_json(self):
-        return {
-            'id': self.id,
-            'user_id': self.user_id,
-            'group_id': self.group_id,
-            'group_name': self.group_name,
-            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
-            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S'),
-        }
-
-
-# 项目配置表
-class SpaceModel(SurrogatePK, Model):
-    # 表的名字:
-    __tablename__ = 'spaces'
-    current_time = datetime.now()
-    status_close = 0
-    status_open = 1
-
-    # 表的结构:
-    id = db.Column(Integer, primary_key=True, autoincrement=True)
-    user_id = db.Column(Integer)
-    name = db.Column(String(100))
-    status = db.Column(Integer)
-
-    created_at = db.Column(DateTime, default=current_time)
-    updated_at = db.Column(DateTime, default=current_time, onupdate=current_time)
-
-    def list(self, page=0, size=10, kw=None):
-        """
-        获取分页列表
-        :param page:
-        :param size:
-        :return:
-        """
-        query = self.query.filter(SpaceModel.status.notin_([self.status_remove]))
-        if kw:
-            query = query.filter(SpaceModel.name.like('%' + kw + '%'))
-
-        # TODO 如果是超管,可以全量,否则需要过滤自己有权限的空间列表
-        if g.role != SUPER:
-            query = query.filter_by(user_id=g.id)
-        count = query.count()
-        data = query.order_by(SpaceModel.id.desc()).offset(int(size) * int(page)).limit(size).all()
-
-        uid2name = UserModel.uid2name(data=data)
-        list = [p.to_json(uid2name) for p in data]
-        return list, count
-
-    def item(self, id=None):
-        """
-        获取单条记录
-        :param role_id:
-        :return:
-        """
-        id = id if id else self.id
-        data = self.query.filter_by(id=id).first()
-        members = MemberModel(group_id=id).members()
-
-        if not data:
-            return []
-
-        data = data.to_json()
-
-        return dict(data, **members)
-
-    def add(self, *args, **kwargs):
-        # todo permission_ids need to be formated and checked
-        data = dict(*args)
-
-        # tag = TagModel(name=data['name'], label='user_group')
-        # db.session.add(tag)
-        # db.session.commit()
-        data = dict(*args)
-        space = SpaceModel(**data)
-        db.session.add(space)
-        db.session.commit()
-
-
-        self.id = space.id
-        return self.id
-
-    def update(self, *args, **kwargs):
-        # todo permission_ids need to be formated and checked
-        # a new type to update a model
-
-        update_data = dict(*args)
-        return super(SpaceModel, self).update(**update_data)
-
-    def remove(self, space_id=None):
-        """
-
-        :param space_id:
-        :return:
-        """
-        space_id = space_id if space_id else self.id
-        SpaceModel.query.filter_by(id=space_id).update({'status': self.status_remove})
-
-        ret = db.session.commit()
-
-        return ret
-
-    def to_json(self, uid2name=None):
-        item = {
-            'id': self.id,
-            'user_id': self.user_id,
-            'user_name': uid2name[self.user_id] if uid2name and self.user_id in uid2name else '',
-            # TODO
-            'group_id': 'self.group_id',
-            'name': self.name,
-            'status': self.status,
-            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
-            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S'),
-        }
-        item.update(self.enable())
-        return item
-
-    def enable(self):
-        return {
-            'enable_update': permission.enable_uid(self.user_id) or permission.enable_role(OWNER),
-            'enable_delete': permission.enable_uid(self.user_id) or permission.enable_role(OWNER),
-            'enable_create': False,
-            'enable_online': False,
-            'enable_audit': False,
-            'enable_block': permission.enable_role(MASTER),
-        }
-

+ 40 - 4
walle/service/deployer.py

@@ -44,9 +44,9 @@ class Deployer:
     console = False
 
     version = datetime.now().strftime('%Y%m%d%H%M%s')
-    project_name = 'walden'
+    project_name = None
     dir_codebase = '/tmp/walle/codebase/'
-    dir_codebase_project = dir_codebase + project_name
+    dir_codebase_project = ''
 
     # 定义远程机器
     # env.hosts = ['172.16.0.231', '172.16.0.177']
@@ -66,15 +66,22 @@ class Deployer:
 
         if task_id:
             self.task_id = task_id
+            # task start
+            TaskModel(id=self.task_id).update(status=TaskModel.status_doing)
+
             self.taskMdl = TaskModel().item(self.task_id)
             self.user_id = self.taskMdl.get('user_id')
             self.servers = self.taskMdl.get('servers_info')
             self.task = self.taskMdl.get('target_user')
             self.project_info = self.taskMdl.get('project_info')
+
         if project_id:
             self.project_id = project_id
             self.project_info = ProjectModel(id=project_id).item()
 
+        self.project_name = self.project_info['id']
+        self.dir_codebase_project = self.dir_codebase + str(self.project_name)
+
         # start to deploy
 
 
@@ -117,8 +124,10 @@ class Deployer:
         result = self.local.run(command, wenv=self.config())
         current_app.logger.info(command)
 
-        time.sleep(3)
         # 检查 目录是否存在
+        self.init_repo()
+
+        # TODO to be removed
         command = 'mkdir -p %s' % (self.dir_codebase_project)
         # TODO remove
         current_app.logger.info(command)
@@ -321,6 +330,8 @@ class Deployer:
             result = waller.run(command, wenv=self.config())
 
     def list_tag(self):
+        self.init_repo()
+
         with self.local.cd(self.dir_codebase_project):
             command = 'git tag -l'
             current_app.logger.info('cd %s  command: %s  ', self.dir_codebase_project, command)
@@ -331,9 +342,11 @@ class Deployer:
         return None
 
     def list_branch(self):
+        self.init_repo()
+
         with self.local.cd(self.dir_codebase_project):
             command = 'git pull'
-            # result = self.local.run(command, wenv=self.config())
+            result = self.local.run(command, wenv=self.config())
 
             current_app.logger.info(self.dir_codebase_project)
 
@@ -351,6 +364,8 @@ class Deployer:
         return None
 
     def list_commit(self, branch):
+        self.init_repo()
+
         with self.local.cd(self.dir_codebase_project):
             command = 'git checkout %s && git pull' % (branch)
             result = self.local.run(command, wenv=self.config())
@@ -371,6 +386,27 @@ class Deployer:
 
         return None
 
+    def init_repo(self):
+
+        current_app.logger.info('git dir: %s', self.dir_codebase_project + '/.git')
+        # 如果项目底下有 .git 目录则认为项目完整,可以直接检出代码
+        # TODO 不标准
+        if not os.path.exists(self.dir_codebase_project):
+            # 检查 目录是否存在
+            command = 'mkdir -p %s' % (self.dir_codebase_project)
+            # TODO remove
+            current_app.logger.info(command)
+            result = self.local.run(command, wenv=self.config())
+
+        if not os.path.exists(self.dir_codebase_project + '/.git'):
+            # 否则当作新项目检出完整代码
+            with self.local.cd(self.dir_codebase_project):
+                command = 'pwd && git clone %s .' % (self.project_info['repo_url'])
+                current_app.logger.info('cd %s  command: %s  ', self.dir_codebase_project, command)
+
+                result = self.local.run(command, wenv=self.config())
+
+
     def walle_deploy(self):
         self.prev_deploy()
         self.deploy()

+ 13 - 0
walle/templates/socketio.html

@@ -32,6 +32,19 @@
         ///                      'event': 'pusher:construct',
         ///                       'data': {"status":0,"success":"","sequence":1,"cmd":"python --version","host":"127.0.0.1","error":""}
         ///                  })
+        ///
+        ///     1 页面刷新时,任务已经部署中
+        ///         a.前端获取历史
+        ///         b.前端接受server端的console
+        ///     2 页面刷新时,任务未开始,点「开始」时已在其它窗口开始部署
+        ///         a.后端不会重新开始这个任务
+        ///         b.前端接受server端的console
+        ///     3 页面刷新时,任务已经部署成功
+        ///         a.前端获取历史
+        ///     4 页面刷新时,任务已经部署失败
+        ///         a.前端获取历史
+        ///         b.前端可高亮「开始」, 用户继续重新部署
+        ///
         ///     client              server
         ///     -------------|------------
         ///     connect      ->