class EntryTag(BaseTable): __tablename__ = 'entry_tag' __export__ = { const.ACL_READ: ['entry_id', 'tag_id', 'tag_class', 'data'] } entry_id = db.Column(db.BigInteger, db.ForeignKey('entry.id'), primary_key=True) tag_id = db.Column(db.BigInteger, db.ForeignKey('tag.id'), primary_key=True) tag_class = db.Column(db.String(255)) data = db.Column(sa_utils.JSONType()) tag = relationship("Tag", backref="entry_assocs") __table_args__ = ( db.UniqueConstraint("entry_id", "tag_id"), ) @classmethod def update_tags(cls_, entry, utags): cls_.delete({'entry_id': entry.id}) for tag_class, tags in utags.items(): for tag in tags: if isinstance(tag, int): el = cls_(entry_id=entry.id, tag_id=tag, tag_class=tag_class) el.save() else: el = cls_(entry_id=entry.id, tag_id=tag['id'], tag_class=tag_class, data=tag) el.save()
class Action(ObjectTable): __tablename__ = 'action' __export__ = { const.ACL_READ: [ 'id', 'action_type', 'user_id', 'object_id', 'domain_id', 'data', 'created_at' ] } action_type = db.Column(db.Integer) user_id = db.Column(db.BigInteger, db.ForeignKey('user.id')) object_id = db.Column(db.BigInteger) domain_id = db.Column(db.BigInteger, db.ForeignKey('domain.id')) data = db.Column(sa_utils.JSONType()) @classmethod def mark(cls_, current_user, action_type, data, domain_id=None): r = { 'user_id': current_user.id, 'action_type': action_types[action_type], 'data': data } if domain_id is not None: r['domain_id'] = domain_id action = cls_(**r) action.save() return action @classmethod def af_find(cls_, current_user, data): q = ActionQuery(current_user) q.assign_request(data) return q.execute()
class DomainTagclass(ObjectTable): __tablename__ = 'domain_tagclass' domain_id = db.Column(db.BigInteger, db.ForeignKey('domain.id'), index=True, nullable=True) tag_class = db.Column(db.String(255), index=True, nullable=False) state = db.Column(sa_utils.JSONType()) @classmethod def af_domain_state(cls_, current_user, domain_id): rsp = {'exclude_tag_classes': []} rows = cls_.find(domain_id=domain_id).all() for row in rows: if 'exclude_display' in row.state and row.state[ 'exclude_display'] is True: rsp['exclude_tag_classes'].append(row.tag_class) return rsp @classmethod def af_update_tag_class(cls_, current_user, tag_class, data): cls_.delete({'tag_class': tag_class}) rows = {} if 'exclude_domain_ids' in data: for domain_id in data['exclude_domain_ids']: rows[domain_id] = {'exclude_display': True} for domain_id, state in rows.items(): udate = { 'domain_id': domain_id, 'tag_class': tag_class, 'state': state } el = cls_(**udate) el.save() return cls_.af_get_tag_class_states(current_user, tag_class) @classmethod def af_get_tag_class_states(cls_, current_user, tag_class): rsp = {'exclude_domain_ids': []} rows = cls_.find(tag_class=tag_class).all() for row in rows: if 'exclude_display' in row.state and row.state[ 'exclude_display'] is True: rsp['exclude_domain_ids'].append(row.domain_id) return rsp
class Geoname(ObjectTable): __tablename__ = 'geoname' __export__ = { const.ACL_READ: ['id', 'parent_id', 'name', 'asciiname', 'name_alternate', 'latitude', 'longitude', 'country_code', 'feature_code'] } parent_id = db.Column(db.BigInteger) name = db.Column(db.String(255)) asciiname = db.Column(db.String(255)) name_alternate = db.Column(sa_utils.ScalarListType(str)) latitude = db.Column(db.Float()) longitude = db.Column(db.Float()) country_code = db.Column(db.String(10)) feature_code = db.Column(db.String(10)) @classmethod def af_autocomplete(cls_, country_code, value): ix = open_dir(os.path.dirname(os.path.realpath(__file__)) + '/../../indexer', indexname="adms") ids = [] rsp = [] with ix.searcher() as s: qp = qparser.QueryParser("names", schema=ix.schema) results = s.search(qp.parse(value), limit=20, filter=query.Term("country_code", country_code)) for res in results: ids.append(res['gid']) if len(ids) > 0: rows = cls_.get_query().filter(cls_.id.in_((ids))).all() for row in rows: data = row.jsonify(acl=const.ACL_READ) data['location_id'] = data['id'] rsp.append(data) return {'results': rsp}
class DomainUser(ObjectTable): __tablename__ = 'domain_user' user_id = db.Column(db.BigInteger, db.ForeignKey('user.id'), index=True, nullable=False) domain_id = db.Column(db.BigInteger, db.ForeignKey('domain.id'), index=True, nullable=True) @classmethod def add_connection(cls_, user_id, domain_id): domain_user = cls_(user_id=user_id, domain_id=domain_id) domain_user.save() return domain_user
class ObjectTable(BaseTable): __abstract__ = True default_cmp_user_id = 'user_id' id = db.Column(db.BigInteger, primary_key=True) created_at = db.Column(db.DateTime, default=db.func.now()) updated_at = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now()) @classmethod def get(cls_, obj_id, required=False): return cls_.find_first({'id': obj_id}, required=required) @classmethod def find_first(cls_, args, required=False): q = db.session.query(cls_) for k, v in args.items(): q = q.filter(getattr(cls_, k) == v) if not required: return q.first() else: obj = q.first() if not hasattr(obj, 'id') or obj.id < 1: raise ResourceNotFoundError(cls_.__name__ + ' not found', object_id=id) return obj @classmethod def get_query(cls_): return db.session.query(cls_) @classmethod def get_restricted(cls_, current_user, obj_id, cmp_fieldname=None): if cmp_fieldname is None: cmp_fieldname = cls_.default_cmp_user_id row = cls_.get(obj_id, required=True) if current_user.id != getattr(row, cmp_fieldname): raise FrobiddenError('No access to object {0:d}'.format(row.id)) return row
class Binbag(ObjectTable): __tablename__ = 'binbag' __export__ = {const.ACL_READ: ['id', 'name', 'mime', 'reference']} name = db.Column(db.String(255)) reference = db.Column(db.String(64)) mime = db.Column(db.String(255)) validate_schema = {"#name": "string", "#mime": "string"} validate_save = validator.parser(validate_schema, flip_hash='+') validate_update = validator.parser(validate_schema, flip_hash='?') @classmethod def af_save(cls_, current_user, data, file=None, obj_id=None): if obj_id is not None: data = cls_.validate_update.validate(data) binbag = cls_.get(obj_id, required=True) binbag.update(**data) else: data = cls_.validate_save.validate(data) data['reference'] = str(uuid.uuid4()) binbag = cls_(**data) binbag.save() if file is not None: # we do it through a file because mysql is shit file.save("%s/%s" % (current_app.config['BINBAG_DIR'], binbag.reference)) return binbag.jsonify(acl=const.ACL_READ) def get_content(self): with open("%s/%s" % (current_app.config['BINBAG_DIR'], self.reference), 'rb') as file_: return file_.read()
class Domain(ObjectTable): __tablename__ = 'domain' __export__ = { const.ACL_READ: ['id', 'name', 'name_display', 'description', 'restrict_countries', 'event_types'] } name = db.Column(db.String(255)) name_display = db.Column(db.String(255)) description = db.Column(db.Text) status = db.Column(db.SmallInteger, default=const.STATUS_ACTIVE) restrict_countries = db.Column(sa_utils.JSONType()) event_types = db.Column(sa_utils.JSONType()) validate_schema = { "#name": "string", "#name_display": "string", "?description": "string", "?restrict_countries": [validator.Enum([country.alpha2 for country in pycountry.countries])], "?event_types": [validator.Tag(tag_class='event_type')] } validate_save = validator.parser(validate_schema, flip_hash='+') validate_update = validator.parser(validate_schema, flip_hash='?') @classmethod def af_save(cls_, current_user, data, obj_id=None): if obj_id is not None: data = cls_.validate_update.validate(data) domain = cls_.get(obj_id, required=True) domain.update(**data) else: data = cls_.validate_save.validate(data) domain = cls_(**data) domain.save() return domain.jsonify(acl=const.ACL_READ) @classmethod def af_find(cls_, current_user, data): q = DomainQuery(current_user) q.assign_request(data) return q.execute()
class Entry(ObjectTable): __tablename__ = 'entry' __export__ = { const.ACL_READ: ['id', 'user_id', 'domain_id', 'lead_id', 'status', 'name', 'country_code', 'created_at', 'excerpt', 'severity', 'reliability', 'status_ord', 'timeline', 'information_at'] } status = db.Column(db.SmallInteger, default=const.STATUS_ACTIVE) user_id = db.Column(db.BigInteger, db.ForeignKey('user.id'), index=True) lead_id = db.Column(db.BigInteger, db.ForeignKey('lead.id'), index=True) domain_id = db.Column(db.BigInteger, db.ForeignKey('domain.id'), index=True) name = db.Column(db.Text) country_code = db.Column(db.String(3)) excerpt = db.Column(db.Text) tags = relationship("EntryTag", backref="entry") locations = relationship("EntryLocation", backref="entry") severity = db.Column(db.Integer) reliability = db.Column(db.Integer) status_ord = db.Column(db.Integer) timeline = db.Column(db.Integer) information_at = db.Column(db.DateTime) validate_schema = { "#lead_id": "integer", "#severity": "integer", "#reliability": "integer", "?status": validator.Enum([const.STATUS_ACTIVE, const.STATUS_INACTIVE, const.STATUS_DELETED]), "?status_ord": validator.Tag(tag_class='status'), "?timeline": validator.Tag(tag_class='timeline'), "?country_code": validator.Enum([None] + [country.alpha2 for country in pycountry.countries]), "#tags": { '?sector': [validator.Tag(tag_class='sector')], '?vulnerable': [validator.TagBlock(tag_class='vulnerable')], '?affected': [validator.TagBlock(tag_class='affected')], '?underlying': [validator.Tag(tag_class='underlying')], '?crisis_driver': [validator.Tag(tag_class='crisis_driver')] }, "#locations": [{ "+source": validator.Enum([const.LOCATION_SOURCE_GEONAME, const.LOCATION_SOURCE_GOOGLE_MAP_SHAPE, const.LOCATION_SOURCE_SELF]), "+location_id": validator.AnyOf("string", "integer"), "+asciiname": "string", "?data": validator.AnyDict() }], "#excerpt": "string", "?information_at": validator.Timestamp() } validate_save = validator.parser(copy.copy(validate_schema), flip_hash='+') validate_update = validator.parser(copy.copy(validate_schema), flip_hash='?') @classmethod def af_save(cls_, current_user, data, obj_id=None): from sidr import models # cuz js is lame if 'lead_id' in data: data['lead_id'] = int(data['lead_id']) if obj_id is not None: action_type = 'EDIT_ENTRY' data = cls_.validate_update.validate(data) entry_data = {key: value for (key, value) in data.items() if key not in ['tags', 'locations']} entry = cls_.get(obj_id, required=True) entry.user_id = current_user.id entry.update(**entry_data) else: action_type = 'ADD_ENTRY' data = cls_.validate_save.validate(data) lead = models.Lead.get(data['lead_id'], required=True) data['user_id'] = current_user.id data['lead_id'] = lead.id data['domain_id'] = lead.domain_id entry_data = {key: value for (key, value) in data.items() if key not in ['tags', 'locations']} entry = cls_(**entry_data) entry.save() if 'locations' in data: models.EntryLocation.update_locations(entry, data['locations']) if 'tags' in data: models.EntryTag.update_tags(entry, data['tags']) models.Action.mark(current_user, action_type, entry.jsonify(acl=const.ACL_READ), domain_id=entry.domain_id) return entry.jsonify_complete(acl=const.ACL_READ) @classmethod def af_find(cls_, current_user, data, rtype='json'): q = EntryQuery(current_user, rtype=rtype) q.assign_request(data) return q.execute() @classmethod def get_overview(cls_, current_user, domain_id): sql = 'SELECT SUM(IF(TO_DAYS(NOW()) - TO_DAYS( created_at ) <= 1, 1, 0)) AS entries_today, SUM(IF(status=1, 1,0)) AS entries_active, SUM(IF(status=2, 1,0)) AS entries_inactive ' for i in range(1, 7): sql += ' ,SUM(IF(status=1 AND severity={0}, 1,0)) AS severity_{0}_active, SUM(IF(status=2 AND severity={0}, 1,0)) AS severity_{0}_inactive'.format(i) sql += ' FROM entry WHERE domain_id={0}'.format(int(domain_id)) row = db.session.execute(sql).first() if row is None or row['entries_today'] is None: return {} rsp = { 'today': int(row['entries_today']), 'active': int(row['entries_active']), 'inactive': int(row['entries_inactive']), 'severity': {} } for i in range(1, 7): rsp['severity'][i] = { 'active': int(row['severity_%s_active' % i]), 'inactive': int(row['severity_%s_inactive' % i]) } return rsp def jsonify_complete(self, acl): rsp = self.jsonify(acl=acl) if self.tags is not None: utags = {} for tag in self.tags: if tag.tag_class not in utags: utags[tag.tag_class] = [] utag = { 'id': tag.tag_id } if isinstance(tag.data, dict): utag.update(tag.data) utags[tag.tag_class].append(utag) rsp['tags'] = utags if self.locations is not None: rsp['locations'] = [] for location in self.locations: rsp['locations'].append(location.jsonify(acl=const.ACL_READ)) if self.user is not None: rsp['user'] = self.user.jsonify(acl=const.ACL_READ) if self.lead is not None: rsp['lead'] = self.lead.jsonify(acl=const.ACL_READ) return rsp
class EntryLocation(BaseTable): __tablename__ = 'entry_location' __export__ = { const.ACL_READ: ['location_id', 'source', 'asciiname', 'country_code', 'data'] } entry_id = db.Column(db.BigInteger, db.ForeignKey('entry.id'), primary_key=True) location_id = db.Column(db.String(255), primary_key=True, autoincrement=False) source = db.Column(db.BigInteger, primary_key=True, autoincrement=False) asciiname = db.Column(db.String(255)) data = db.Column(sa_utils.JSONType()) country_code = db.Column(db.String(3)) __table_args__ = (db.UniqueConstraint("entry_id", "location_id", "source"), ) @classmethod def update_locations(cls_, entry, locations): cls_.delete({'entry_id': entry.id}) for data in locations: udata = { 'location_id': data['location_id'], 'source': data['source'], 'asciiname': data['asciiname'], 'data': data['data'] if 'data' in data else None, 'country_code': entry.country_code, 'entry_id': entry.id } el = cls_(**udata) el.save() @classmethod def get_overview(cls_, current_user, domain_id): sql = 'SELECT location_id, data, source, entry_id, entry.severity, entry_location.asciiname, geoname.latitude, geoname.longitude FROM entry_location' sql += ' INNER JOIN entry ON (entry.id=entry_location.entry_id)' sql += ' LEFT JOIN geoname ON (geoname.id=location_id AND source=%s)' % const.LOCATION_SOURCE_GEONAME sql += ' WHERE entry.status !=%s AND domain_id=%s' % ( const.STATUS_DELETED, int(domain_id)) rows = db.session.execute(sql) rsp = [] for row in rows: rsp.append({ 'location_id': row['location_id'], 'source': row['source'], 'entry_id': row['entry_id'], 'severity': row['severity'], 'asciiname': row['asciiname'], 'latitude': row['latitude'], 'longitude': row['longitude'], 'data': json.loads(row['data']) if row['data'] is not None else None }) return rsp
class User(ObjectTable, UserMixin): __tablename__ = 'user' __export__ = { const.ACL_OWNER: ['id', 'name', 'email', 'orgnization', 'status', 'role', 'state'], const.ACL_READ: ['id', 'name', 'orgnization', 'role'] } default_cmp_user_id = 'id' email = db.Column(db.String(255), unique=True, index=True) name = db.Column(db.String(255)) orgnization = db.Column(db.String(255)) password = db.Column(sa_utils.PasswordType(schemes=['pbkdf2_sha512'])) signature = db.Column(db.String(64), default=random_string) status = db.Column(db.SmallInteger, default=const.STATUS_ACTIVE) role = db.Column(db.SmallInteger, default=const.ROLE_USER) state = db.Column(sa_utils.JSONType()) leads = db.relationship("Lead", backref="user") entries = db.relationship("Entry", backref="user") actions = db.relationship("Action", backref="user") __table_args__ = (db.UniqueConstraint('email'), ) validate_save = validator.parser({ "+email": validator.Email, "+name": "string", "+password": "******", "?state": { 'focus_domain_id': "integer" } }) validate_update = validator.parser({ "?email": validator.Email, "?name": "string", "?password": "******", "?state": { 'focus_domain_id': "integer" } }) @classmethod def af_save(cls_, current_user, data, obj_id=None): try: if obj_id is not None: user = cls_.get_restricted(current_user, obj_id, 'id') data = cls_.validate_update.validate(data) user.update(**data) return user.jsonify(current_user), user else: data = cls_.validate_save.validate(data) user = cls_(**data) user.save() return user.jsonify(acl=const.ACL_OWNER), user except IntegrityError: raise ApiValidationError(ValidationError('Email already exists')) @classmethod def af_map(cls_, current_user): return cls_.af_find(current_user, {'status': const.STATUS_ACTIVE}) @classmethod def af_find(cls_, current_user, data): q = UserQuery(current_user) q.assign_request(data) return q.execute() @classmethod def af_reset_send(cls_, email, returnurl): user = cls_.find_first({'email': email}) if not user: raise ApiValidationError(ValidationError('Unknown email')) serializer = JSONWebSignatureSerializer(current_app.config['API_KEY']) resettoken = serializer.dumps({ "id": user.id, "signature": user.signature }).decode('utf-8') content = 'Click here to reset your password: %s' % (returnurl + resettoken) mailer.send(user.email, user.name, 'Password reset link', content) return {'success': True} @classmethod def af_reset_recieve(cls_, token, password): serializer = JSONWebSignatureSerializer(current_app.config['API_KEY']) try: payload = serializer.loads(token) user = cls_.get(payload['id']) if user is None or payload[ 'signature'] != user.signature or user.status != const.STATUS_ACTIVE: raise ApiValidationError( ValidationError('User reset not allowed')) user.update(password=password, signature=random_string()) return user.jsonify(acl=const.ACL_OWNER) except (itsdangerous.SignatureExpired, itsdangerous.BadSignature): raise ApiValidationError(ValidationError('Token is broken')) def connect_domain(self, domain_id): from sidr.models import DomainUser return DomainUser.add_connection(self.id, domain_id)
class Lead(ObjectTable): __tablename__ = 'lead' __export__ = { const.ACL_READ: [ 'id', 'user_id', 'assignee_id', 'domain_id', 'lead_type', 'status', 'name', 'data', 'confidentiality', 'created_at', 'published_at', 'binbags', 'source_id', 'content_format_id', 'website', 'url' ] } user_id = db.Column(db.BigInteger, db.ForeignKey('user.id'), index=True) assignee_id = db.Column(db.BigInteger) domain_id = db.Column(db.BigInteger, db.ForeignKey('domain.id'), index=True) source_id = db.Column(db.BigInteger, db.ForeignKey('tag.id'), index=True) content_format_id = db.Column(db.BigInteger, db.ForeignKey('tag.id'), index=True) lead_type = db.Column(db.String(255)) binbags = db.Column(sa_utils.JSONType()) status = db.Column(db.SmallInteger, default=const.STATUS_PENDING) name = db.Column(db.String(255)) description = db.Column(db.Text) website = db.Column(db.String(255)) confidentiality = db.Column(db.SmallInteger, default=const.CONFIDENTIALITY_UNPROTECTED) url = db.Column(db.Text) published_at = db.Column(db.DateTime) entries = db.relationship("Entry", backref="lead") validate_schema = { "?name": "string", "?status": validator.Enum([ const.STATUS_ACTIVE, const.STATUS_INACTIVE, const.STATUS_PENDING, const.STATUS_DELETED ]), "#domain_id": "integer", "?confidentiality": "integer", "#lead_type": validator.Enum(lead_type_dict.keys()), "#source_id": validator.Tag(tag_class='source'), "?content_format_id": validator.Tag(tag_class='content_format'), "?description": "string", "?binbags": [{ "mime": "string", "name": "string", "reference": "string", "id": "integer" }], "?published_at": validator.Timestamp() } validate_save = validator.parser(copy.copy(validate_schema), flip_hash='+', additional_properties=True) validate_update = validator.parser(copy.copy(validate_schema), flip_hash='?', additional_properties=True) @classmethod def af_save(cls_, current_user, data, obj_id=None): from sidr import models if obj_id is not None: action_type = 'EDIT_LEAD' data = cls_.validate_update.validate(data) lead = cls_.get(obj_id, required=True) if lead.lead_type is not None: data = lead_type_dict[lead.lead_type].validate_update.validate( data) lead.user_id = current_user.id lead.update(**data) else: action_type = 'ADD_LEAD' data = cls_.validate_save.validate(data) data = lead_type_dict[data['lead_type']].validate_save.validate( data) data['user_id'] = current_user.id lead = cls_(**data) lead.save() models.Action.mark(current_user, action_type, lead.jsonify(acl=const.ACL_READ), domain_id=lead.domain_id) return lead.jsonify(acl=const.ACL_READ) @classmethod def af_find(cls_, current_user, data, rtype='json'): q = LeadQuery(current_user, rtype=rtype) q.assign_request(data) return q.execute() @classmethod def get_overview(cls_, current_user, domain_id): sql = 'SELECT SUM(IF(TO_DAYS(NOW()) - TO_DAYS( created_at ) <= 1, 1, 0)) AS leads_today, SUM(IF(status=1, 1,0)) AS leads_active, SUM(IF(status=3, 1,0)) AS leads_pending from lead' sql += ' WHERE domain_id={0}'.format(int(domain_id)) row = db.session.execute(sql).first() if row is None or row['leads_today'] is None: return {} return { 'today': int(row['leads_today']), 'active': int(row['leads_active']), 'pending': int(row['leads_pending']), }
class Location(ObjectTable): __tablename__ = 'location' __export__ = { const.ACL_READ: ['id', 'code', 'level', 'parent_id', 'longtitude', 'latitude', 'name', 'country_code'] } name = db.Column(db.String(255)) code = db.Column(db.String(255), index=True) level = db.Column(db.String(255)) parent_code = db.Column(db.String(255), index=True) longtitude = db.Column(db.Integer) latitude = db.Column(db.Integer) country_code = db.Column(db.String(3)) tree = db.Column(sa_utils.JSONType()) status = db.Column(db.SmallInteger, default=const.STATUS_ACTIVE) validate_schema = { "#name": "string", "#code": "string", "?level": "string", "?longtitude": "integer", "?latitude": "integer", "?parent_code": "string", "#country_code": validator.Enum([country.alpha2 for country in pycountry.countries]) } validate_save = validator.parser(copy.copy(validate_schema), flip_hash='+', additional_properties=True) validate_update = validator.parser(copy.copy(validate_schema), flip_hash='?', additional_properties=True) def deduce_tree(self): if len(self.parent_code) < 1: self.update(level=1) return tree = [] level = 2 f_code = self.parent_code while(True): p = Location.find_first({'code': f_code}) tree.insert(0, {'code': p.code, 'name': p.name, 'id': p.id}) if len(p.parent_code) < 1: break else: f_code = p.parent_code level = level + 1 self.update(level=level, tree=tree) @classmethod def af_save(cls_, current_user, data, obj_id=None): if obj_id is not None: data = cls_.validate_update.validate(data) location = cls_.get(obj_id, required=True) location.update(**data) else: data = cls_.validate_save.validate(data) location = cls_(**data) location.save() return location.jsonify(acl=const.ACL_READ) @classmethod def bsave(cls_, data): location = cls_.find_first({'country_code': data['country_code'], 'code': data['code']}) if location is None: location = cls_(**data) location.save() else: location.update(**data) location.deduce_tree() @classmethod def af_find(cls_, current_user, data): q = LocationQuery(current_user) q.assign_request(data) return q.execute() @classmethod def af_autocomplete(cls_, country_code, value): rsp = [] rows = cls_.get_query().filter(db.or_(cls_.name.like("%" + value + "%"), cls_.code.like("%" + value + "%"))).filter(cls_.country_code == country_code).limit(15).all() for row in rows: rsp.append(row.jsonify(acl=const.ACL_READ)) return {'results': rsp}
class Tag(ObjectTable): __tablename__ = 'tag' __export__ = { const.ACL_READ: ['id', 'tag_class', 'name', 'data', 'restricted_domains', 'tree'] } tag_class = db.Column(db.String(255)) status = db.Column(db.SmallInteger, default=const.STATUS_ACTIVE) name = db.Column(db.String(255)) data = db.Column(sa_utils.JSONType()) restrict_domains = db.Column(sa_utils.ScalarListType()) parent_id = db.Column(db.BigInteger, db.ForeignKey('tag.id')) tree = db.Column(sa_utils.JSONType()) validate_save = validator.parser( { "+tag_class": validator.Enum(tag_class_dict.keys()), "?parent_id": validator.Integer() }, additional_properties=True) @classmethod def af_tag_classes(cls_): rsp = [] for name, obj in tag_class_dict.items(): rsp.append({'name': name, 'metadata': obj.jsonify_metadata()}) return {'result': rsp, 'total': len(rsp)} @classmethod def af_delete(cls_, obj_id): tag = cls_.get(obj_id, required=True) tag.update(**{'status': const.STATUS_DELETED}) return tag.jsonify(acl=const.ACL_READ) @classmethod def af_save(cls_, current_user, data, obj_id=None): data = cls_.validate_save.validate(data) data = tag_class_dict[data['tag_class']].validate(data, obj_id) if 'parent_id' in data: parents = cls_.get_parenthood(data['parent_id']) tree = [] for parent in parents: tree.append({ 'id': parent.id, 'name': parent.name, 'title': parent.data['title'] }) data['tree'] = tree if obj_id is not None: tag = cls_.get(obj_id, required=True) tag.update(**data) return tag.jsonify(acl=const.ACL_READ) else: tag = cls_(**data) tag.save() return tag.jsonify(acl=const.ACL_READ) @classmethod def af_find(cls_, current_user, data): q = TagQuery(current_user) q.assign_request(data) return q.execute() @classmethod def get_parenthood(cls_, obj_id, parents=None): if parents is None: parents = [] if obj_id == 0 or obj_id is None: return parents obj = cls_.get(obj_id) parents.append(obj) return cls_.get_parenthood(obj.parent_id, parents=parents) @classmethod def get_id_map(cls_): umap = {} rows = cls_.find().all() for row in rows: umap[row.id] = row.name return umap def jsonify(self, acl=None): return tag_class_dict[self.tag_class].jsonify_tag(self)