def add_zone(): """ INPUT: names: list of str """ names = request.json['names'] z_o, _ = g.user.zones(need_list=False) z_o = z_o.get(None, []) if len(z_o) + len(names) > current_app.config['LIMIT_ZONES']: flash('课程数量超出限制', 'error') g.action_success = False return cur = mysql.get_db().cursor() z_onew = z_o for name in names: if len(name) > current_app.config['MAX_NAME_LENGTH']: flash('名称长度超出限制', 'error') g.action_success = False return cur.execute( ''' insert into zones (next_zid, name, uid) values (null, %s, %s) ''', [name, g.user.uid]) z_onew = z_onew + [cur.lastrowid] model.update_linkedlist(z_o, z_onew, 'zones')
def delete_zone(): """ INPUT: ids: list of int """ zids=list(set(request.json['ids'])) z_o,_=g.user.zones(need_list=False) z_o=z_o.get(None,[]) z_onew=z_o[:] for zid in zids: if zid not in z_o: flash('课程不存在或没有权限','error') g.action_success=False return cur=mysql.get_db().cursor() for zid in zids: cur.execute(''' delete from zones where zid=%s and uid=%s ''',[zid,g.user.uid]) z_onew.remove(zid) model.update_linkedlist(z_o,z_onew,'zones')
def proc_extpid(name_list): """ Convert share pattern to external pids. :param name_list: list of string which may contain share pattern :return: yields a list of [name, extpid or None] """ cur = mysql.get_db().cursor() for nameline in name_list: name, at, share_hash = nameline.rpartition('@@') if not at: # '','','name' yield [share_hash, None] else: # 'name','@@','hash' if not share_hash: raise SisterErrorMsg(nameline + ':分享ID无效') cur.execute( ''' select uid,pid from projects where share_hash=%s ''', [share_hash]) res = cur.fetchone() if not res: raise SisterErrorMsg(nameline + ':未找到分享ID') if res[0] == g.user.uid: raise SisterErrorMsg(nameline + ':不能分享给自己') yield [name, res[1]]
def update_linkedlist(before, after, table_name): """ Modify linked list and write to database. :param before: [id...] original linked list status to reduce writes, None for unknown :param after: [id...] new linked list status which will write to db :param table_name: table name, in ['zones','projects','tasks'] """ key_name = { 'zones': 'zid', 'projects': 'pid', 'tasks': 'tid' }.get(table_name, None) if key_name is None: raise ValueError('update_linkedlist table_name is %r' % table_name) cur = mysql.get_db().cursor() # noinspection SqlWithoutWhere sql = 'update %s set next_%s=%%s where %s=%%s and uid=%%s' % ( table_name, key_name, key_name) before = ([] if before is None else before) + [None] after = list(after) + [None] before_nxts = {before[i]: before[i + 1] for i in range(len(before) - 1)} chgs = [] for i in range(len(after) - 1): if after[i + 1] != before_nxts.get(after[i], ...): chgs.append([after[i + 1], after[i], g.user.uid]) if chgs: cur.executemany(sql, chgs)
def delete_task(): """ INPUT: ids: list of int parent_id: int """ tids=list(set(request.json['ids'])) pid=int(request.json['parent_id']) t_o,_=g.user.tasks(pid,need_list=False) t_o=t_o.get(pid,[]) t_onew=t_o[:] for tid in tids: if tid not in t_o: flash('任务不存在或没有权限','error') g.action_success=False return cur=mysql.get_db().cursor() for tid in tids: cur.execute(''' delete from tasks where tid=%s and uid=%s ''',[tid,g.user.uid]) t_onew.remove(tid) model.update_linkedlist(t_o,t_onew,'tasks')
def projects(self, zid=None, *, need_list=True): """ Get projects and their ordering. :param zid: specify zone id to retrive, None to retrive all :param need_list: False if second ret val is unnecessary (will return None) :return: {zid: [pid, ...]}, {pid: {'name','id',...}, ...} """ cur = mysql.get_db().cursor() sql = ''' select pid,next_pid,zid,name,extpid,share_hash,share_name from projects where uid=%s ''' if zid is None: cur.execute(sql, [self.uid]) else: cur.execute(sql + ' and zid=%s', [self.uid, zid]) def recover(_group, ll_dict): update_linkedlist(None, ll_dict.keys(), 'projects') return sort_linkedlist( cur.fetchall(), recover, idx_id=0, idx_next=1, idx_group=2, attrs=[ 'id', None, 'parent_id', 'name', '_extpid', 'share_hash', 'share_name' ] if need_list else None, )
def update_complete(): """ INPUT: ids: list of int completeness: str """ tids = list(request.json['ids']) completeness = str(request.json['completeness']) assert completeness in ['todo', 'done', 'highlight', 'ignored'], 'invalid completeness' if len(tids) > current_app.config['LIMIT_TASKS_PER_PROJECT']: flash('任务数量超出限制', 'error') g.action_success = False return cur = mysql.get_db().cursor() for tid in tids: tid = int(tid) # set status from placeholder to active, if have permission cur.execute( ''' update tasks set status='active' where tid=%s and uid=%s and status='placeholder' ''', [tid, g.user.uid]) # keeping description_idx same cur.execute( ''' insert into completes (uid, tid, completeness, update_timestamp) values (%s, %s, %s, unix_timestamp()) on duplicate key update completeness=%s, update_timestamp=unix_timestamp() ''', [g.user.uid, tid, completeness, completeness])
def add_project(): """ INPUT: names: list of str parent_id: int """ zid = int(request.json['parent_id']) try: names = list(proc_extpid(request.json['names'])) except SisterErrorMsg as e: flash(e.msg, 'error') g.action_success = False return if not g.user.check_zone(zid): flash('课程不存在或没有权限', 'error') g.action_success = False return p_o, _ = g.user.projects(zid, need_list=False) p_o = p_o.get(zid, []) if len(p_o) + len(names) > current_app.config['LIMIT_PROJECTS_PER_ZONE']: flash('类别数量超出限制', 'error') g.action_success = False return already_extpids = set(g.user.imported_extpids()) for name, extpid in names: if extpid: if g.user.ring > current_app.config['MAX_RING_FOR_SHARING']: flash('你所在的用户组不能导入共享', 'error') g.action_success = False return if extpid in already_extpids: flash(name + ':已经添加过了', 'error') g.action_success = False return else: already_extpids.add(extpid) cur = mysql.get_db().cursor() p_onew = p_o for name, extpid in names: if len(name) > current_app.config['MAX_NAME_LENGTH']: flash('名称长度超出限制', 'error') g.action_success = False return cur.execute( ''' insert into projects (next_pid, name, uid, zid, extpid) values (null, %s, %s, %s, %s) ''', [name, g.user.uid, zid, extpid]) p_onew = p_onew + [cur.lastrowid] model.update_linkedlist(p_o, p_onew, 'projects')
def imported_extpids(self): """ Return list of extpids that the user has already imported. :return: list of extpids """ cur = mysql.get_db().cursor() cur.execute( ''' select extpid from projects where uid=%s and extpid is not null ''', [self.uid]) return [x[0] for x in cur.fetchall()]
def get_user_from_token(token): cur = mysql.get_db().cursor() cur.execute( 'select uid,name,ring,splash_index,settings from users where user_token=%s', [token]) res = cur.fetchone() if res: uid, name, ring, splash_index, settings = res return model.User(uid, name, ring, splash_index, settings) else: return None
def check_project(self, pid): """ Check whether the user have control of the given pid. :param pid: int :return: bool """ cur = mysql.get_db().cursor() cur.execute( ''' select count(*) from projects where pid=%s and uid=%s ''', [pid, self.uid]) return cur.fetchone()[0] != 0
def market_search_project(): """ INPUT: term: str OUTPUT: error: str or null error_msg: str if error occurs result: list of {name,share_hash,pid} tasks: list of tasks """ term = str(request.json['term']) if not term: raise SisterErrorMsg('请输入搜索词') if len(term) < 2: raise SisterErrorMsg('请输入至少两个字') cur = mysql.get_db().cursor() if g.user.ring == 0 and term == '.*': cur.execute(''' select name,share_hash,share_name,pid from projects where share_hash is not null order by pid desc limit 0,50 ''') else: cur.execute( ''' select name,share_hash,share_name,pid from projects where match(share_name) against (%s in natural language mode) and share_hash is not null limit 0,25 ''', [term]) res = [ { 'name': name, 'share_hash': '' + share_hash, # will intentionally die here if share_hash is None 'share_name': share_name, 'pid': pid, } for name, share_hash, share_name, pid in cur.fetchall() ] pids = [r['pid'] for r in res] if pids: tasks_o, tasks_li = g.user.tasks(pids, bypass_permission=True, need_completes=False) else: tasks_o = [] tasks_li = {} return { 'error': None, 'result': res, 'tasks_o': tasks_o, 'tasks_li': tasks_li, }
def reset_splash_index(): """ INPUT: none """ cur = mysql.get_db().cursor() cur.execute( ''' update users set splash_index=%s where uid=%s ''', [current_app.config['INITIAL_SPLASH_INDEX'], g.user.uid]) flash('已重置欢迎页面进度', 'success') raise SisterProceed()
def add_task(): """ INPUT: names: list of str parent_id: int task_due_first: int or null task_due_delta: int (days) or null """ names = request.json['names'] pid = int(request.json['parent_id']) task_due_first = request.json['task_due_first'] task_due_delta = int(request.json['task_due_delta']) if task_due_first is not None: task_due_first = int(task_due_first) if not 0 <= task_due_delta < 1000: flash('截止日期间隔错误', 'error') g.action_success = False return if not g.user.check_project(pid): flash('类别不存在或没有权限', 'error') g.action_success = False return t_o, _ = g.user.tasks(pid, need_list=False) t_o = t_o.get(pid, []) if len(t_o) + len(names) > current_app.config['LIMIT_TASKS_PER_PROJECT']: flash('任务数量超出限制', 'error') g.action_success = False return cur = mysql.get_db().cursor() t_onew = t_o for idx, name in enumerate(names): if len(name) > current_app.config['MAX_NAME_LENGTH']: flash('名称长度超出限制', 'error') g.action_success = False return cur.execute( ''' insert into tasks (next_tid, name, uid, pid, status, due) values (null, %s, %s, %s, %s, %s) ''', [ name, g.user.uid, pid, 'placeholder' if len(names) > 1 else 'active', None if task_due_first is None else (task_due_first + idx * 86400 * task_due_delta) ]) t_onew = t_onew + [cur.lastrowid] model.update_linkedlist(t_o, t_onew, 'tasks')
def update_task(): """ INPUT: id: int name: str status: str desc: str or null due: int or null """ tid = int(request.json['id']) name = str(request.json['name']) status = str(request.json['status']) assert status in ['placeholder', 'active'], 'invalid status' due = request.json['due'] desc = request.json['desc'] if due is not None: due = int(due) if desc is not None: desc = str(desc) if len(name) > current_app.config['MAX_NAME_LENGTH']: flash('名称长度超出限制', 'error') g.action_success = False return cur = mysql.get_db().cursor() cur.execute( ''' select description from tasks where tid=%s and uid=%s ''', [tid, g.user.uid]) res = cur.fetchone() if not res: flash('任务不存在或没有权限', 'error') g.action_success = False return prefix_len = str_prefix_length(res[0] or '', desc or '') if prefix_len < len( res[0] or ''): # prefix gets shorter, so need to remove affected desc idx cur.execute( ''' update completes set description_idx=%s where tid=%s and description_idx>%s ''', [prefix_len, tid, prefix_len]) cur.execute( ''' update tasks set name=%s, status=%s, due=%s, description=%s where tid=%s and uid=%s ''', [name, status, due, desc, tid, g.user.uid])
def update_project(): """ INPUT: id: int name: str shared: bool share_name: str """ pid = int(request.json['id']) name = str(request.json['name']) shared = bool(request.json['shared']) share_name = request.json['share_name'] if share_name is not None: share_name = str(share_name) if shared and g.user.ring > current_app.config['MAX_RING_FOR_SHARING']: flash('你所在的用户组不能创建共享', 'error') g.action_success = False return if len(name) > current_app.config['MAX_NAME_LENGTH']: flash('名称长度超出限制', 'error') g.action_success = False return cur = mysql.get_db().cursor() cur.execute( ''' update projects set name=%s where pid=%s and uid=%s ''', [name, pid, g.user.uid]) if shared: # gen share hash if not already shared cur.execute( ''' update projects set share_hash=%s where pid=%s and uid=%s and extpid is null and share_hash is null ''', [gen_share_hash(), pid, g.user.uid]) # update share name cur.execute( ''' update projects set share_name=%s where pid=%s and uid=%s and extpid is null ''', [share_name, pid, g.user.uid]) else: # clear share hash and share name cur.execute( ''' update projects set share_hash=null, share_name=null where pid=%s and uid=%s ''', [pid, g.user.uid])
def update_settings(): """ IPNUT: settings: json object """ settings_obj = request.json['settings'] settings_str = json.dumps(settings_obj) if len(settings_str) > current_app.config['SETTINGS_MAX_BYTES']: flash('设置大小超过限制', 'error') g.action_success = False return cur = mysql.get_db().cursor() cur.execute(''' update users set settings=%s where uid=%s ''', [settings_str, g.user.uid]) raise SisterProceed()
def update_zone(): """ INPUT: id: int name: str """ zid = int(request.json['id']) name = str(request.json['name']) if len(name) > current_app.config['MAX_NAME_LENGTH']: flash('名称长度超出限制', 'error') g.action_success = False return cur = mysql.get_db().cursor() cur.execute( ''' update zones set name=%s where zid=%s and uid=%s ''', [name, zid, g.user.uid])
def update_desc_idx(): """ INPUT: id: int desc_idx: int or null """ tid = int(request.json['id']) desc_idx = request.json['desc_idx'] if desc_idx is not None: desc_idx = int(desc_idx) cur = mysql.get_db().cursor() # keeping completeness same cur.execute( ''' insert into completes (uid, tid, completeness, update_timestamp, description_idx) values (%s, %s, 'todo', unix_timestamp(), %s) on duplicate key update description_idx=%s ''', [g.user.uid, tid, desc_idx, desc_idx])
def __init__(self, uid, name, ring, splash_index, settings): self.uid = uid assert isinstance(self.uid, int), 'uid not integer: %r' % self.uid self.name = name self.ring = ring self.splash_index = splash_index try: self.settings = json.loads(settings) except Exception as e: current_app.logger.exception('unserialize settings %r for user %d', settings, self.uid) flash( 'user settings %r unserialize failed for user %d: %s %s' % (settings, self.uid, type(e), e), 'error') cur = mysql.get_db().cursor() cur.execute( ''' update users set settings='{}' where uid=%s ''', [self.uid]) self.settings = {}
def zones(self, *, need_list=True): """ Get zones and their ordering. :param need_list: False if second ret val is unnecessary (will return None) :return: {None: [zid, ...]}, {zid: {'name','id'}, ...} """ cur = mysql.get_db().cursor() cur.execute( ''' select zid,next_zid,name from zones where uid=%s ''', [self.uid]) def recover(_group, ll_dict): update_linkedlist(None, ll_dict.keys(), 'zones') return sort_linkedlist( cur.fetchall(), recover, idx_id=0, idx_next=1, idx_group=None, attrs=['id', None, 'name'] if need_list else None, )
def tasks(self, pid=None, *, need_list=True, bypass_permission=False, need_completes=True): """ Get tasks and their ordering. :param pid: int or list (for multiple). specify project id to retrive, None to retrive all :param need_list: False if second ret val is unnecessary (will return None) :param bypass_permission: use with `pid`. set to True to remove limit of only fetching tasks of current user :param need_completes: False to skip joining with completes (and always return empty completeness) :return: {pid: [tid, ...]}, {tid: {'name','id','status','due'...}, ...} """ cur = mysql.get_db().cursor() if need_completes: sql = ''' select tasks.tid,next_tid,pid,name,status,due,ifnull(completeness,'todo'),completes.update_timestamp,description,completes.description_idx from tasks left join completes on tasks.tid=completes.tid and completes.uid=%s where 1 ''' sql_args = [g.user.uid] else: sql = ''' select tid,next_tid,pid,name,status,due,'todo',null,description,null from tasks where 1 ''' sql_args = [] if bypass_permission and pid is None: raise ValueError('bypassing permission without pid') if pid is not None: if isinstance(pid, list): if not pid: return {}, {} sql += ' and tasks.pid in %s' sql_args.append(pid) else: sql += ' and tasks.pid=%s' sql_args.append(pid) if not bypass_permission: sql += ' and tasks.uid=%s' sql_args.append(self.uid) cur.execute(sql, sql_args) def recover(_group, ll_dict): update_linkedlist(None, ll_dict.keys(), 'tasks') return sort_linkedlist( cur.fetchall(), recover, idx_id=0, idx_next=1, idx_group=2, attrs=[ 'id', None, 'parent_id', 'name', 'status', 'due', 'completeness', 'complete_timestamp', 'desc', 'desc_idx' ] if need_list else None, )
def decorated(*args, **kwargs): # check api version client_api_ver = request.args.get('sister_ver', None) if client_api_ver not in COMPATIBLE_SISTER_VER: return jsonify({ 'error': 'SISTER_VER_MISMATCH', 'error_msg': 'API 版本与后端 (Sister %s) 不兼容' % (', '.join(COMPATIBLE_SISTER_VER)), }) g.user = None g.action_success = True # check auth and init g.user g.token = request.args.get('user_token', None) if g.token: g.user = get_user_from_token(g.token) if require_ring is not None: if g.user is None: return jsonify({ 'error': 'AUTH_REQUIRED', 'error_msg': '需要登录', 'backend': _backend_value(), }) elif g.user.ring > require_ring: return jsonify({ 'error': 'SISTER_ERROR', 'error_msg': '你所在的用户组不支持此操作', 'backend': _backend_value(), }) # check splash if enforce_splash and g.user: handler = splashes.SplashHandler.get_handler_by_index( g.user.splash_index) if handler is not None: return jsonify({ 'error': 'SPLASH_REQUIRED', 'error_msg': '需要更新前端来完成 %s' % handler.splash_name, 'backend': _backend_value(), 'splash': { 'index': handler.splash_index, 'name': handler.splash_name, 'type': handler.splash_type, 'handout': handler.handout(g.user.uid), }, }) # do original view function try: res = f(*args, **kwargs) # check for default sister response if res is None: if g.user and may_fallback: res = g.user.build_sister_response() else: raise Exception('no available response') # error handling except SisterErrorMsg as e: return jsonify({ 'error': 'SISTER_ERROR', 'error_msg': e.msg, 'backend': _backend_value(), }) except SisterProceed: mysql.get_db().commit() return jsonify({ 'error': 'PROCEED', 'error_msg': '操作完成,请刷新页面', 'backend': _backend_value(), }) except Exception as e: current_app.logger.exception('exception in wrapped handler') return jsonify({ 'error': 'BACKEND_EXCEPTION', 'error_msg': '%s %s' % (type(e), e), 'backend': _backend_value(), }) # post processing else: mysql.get_db().commit() return jsonify({ 'error': None, 'backend': _backend_value(), 'user': None if g.user is None else g.user.user_info(), 'action_success': g.action_success, **res, })
def whole_import(): """ INPUT: data: json object (same format as model output) OUTPUT: error: str or null """ data = request.json['data'] if data['backend']['cache_data_ver'] != IMPORT_DATA_VERSION: return { 'error': '版本不匹配,只支持 %s 但数据版本为 %s' % (IMPORT_DATA_VERSION, data['backend']['cache_data_ver']) } if data['error']: return {'error': '数据有错误'} db = mysql.get_db() cur = db.cursor() z_o, _ = g.user.zones(need_list=False) z_o = z_o.get(None, []) if len(z_o) + len(data['zone_order']) > current_app.config['LIMIT_ZONES']: return {'error': '课程数量超出限制'} if len(z_o) >= len(data['zone_order']): # prevent duplicate import return {'error': '您的账户已有数据,请删除现有课程后再导入'} def isinstance_or_null(x, c): return isinstance(x, c) or x is None next_zid = None for json_zid in data['zone_order'][::-1]: zone = data['zone'][str(json_zid)] if not isinstance(zone['name'], str) or len( zone['name']) > current_app.config['MAX_NAME_LENGTH']: return {'error': '名称长度超出限制'} if len(zone['project_order'] ) > current_app.config['LIMIT_PROJECTS_PER_ZONE']: return {'error': '类别数量超出限制'} cur.execute( ''' insert into zones (next_zid,name,uid) values (%s,%s,%s) ''', [next_zid, zone['name'], g.user.uid]) db_zid = cur.lastrowid next_zid = db_zid next_pid = None for json_pid in zone['project_order'][::-1]: proj = data['project'][str(json_pid)] if not isinstance(proj['name'], str) or len( proj['name']) > current_app.config['MAX_NAME_LENGTH']: return {'error': '名称长度超出限制'} if len(proj['task_order'] ) > current_app.config['LIMIT_TASKS_PER_PROJECT']: return {'error': '任务数量超出限制'} cur.execute( ''' insert into projects (next_pid,name,uid,zid) values (%s,%s,%s,%s) ''', [next_pid, proj['name'], g.user.uid, db_zid]) db_pid = cur.lastrowid next_pid = db_pid next_tid = None for json_tid in proj['task_order'][::-1]: task = data['task'][str(json_tid)] if not isinstance(task['name'], str) or len( task['name']) > current_app.config['MAX_NAME_LENGTH']: return {'error': '名称长度超出限制'} if task['completeness'] not in [ 'todo', 'done', 'highlight', 'ignored' ]: return {'error': '任务完成状态无效'} if task['status'] not in ['placeholder', 'active']: return {'error': '任务布置状态无效'} if not isinstance_or_null(task['desc'], str) or not isinstance_or_null( task['desc_idx'], int): return {'error': '任务备注无效'} if not isinstance_or_null(task['due'], int): return {'error': '任务截止时间无效'} cur.execute( ''' insert into tasks (next_tid, name, uid, pid, status, due, description) values (%s,%s,%s,%s,%s,%s,%s) ''', [ next_tid, task['name'], g.user.uid, db_pid, task['status'], task['due'], task['desc'] ]) db_tid = cur.lastrowid next_tid = db_tid if task['completeness'] != 'todo' or task['desc_idx']: cur.execute( ''' insert into completes (uid, tid, completeness, update_timestamp, description_idx) values (%s,%s,%s,unix_timestamp(),%s) ''', [ g.user.uid, db_tid, task['completeness'], task['desc_idx'] ]) return {'error': None}
def register(): """ ARGS: user_token -> g.token as dealed in sister INPUT: regcode: str """ if not g.token: raise SisterErrorMsg('未登录') regcode = str(request.json['regcode']) cur = mysql.get_db().cursor() try: info = user_control.get_info_from_user_token(g.token) except Exception as e: current_app.logger.exception('get info from user token failed') raise SisterErrorMsg('查询用户信息失败') if info is None: raise SisterErrorMsg('用户不存在') unique_id = info['unique_id'] cur.execute( ''' select count(*) from users where unique_id=%s ''', [unique_id]) if cur.fetchone()[0] > 0: # already registered: update user token cur.execute( ''' update users set user_token=%s where unique_id=%s ''', [g.token, unique_id]) flash('%s,欢迎回来' % info['name'], 'success') else: # register new one try: reg = user_control.check_registration_code(regcode, info) except Exception as e: current_app.logger.exception('check registration code failed') raise SisterErrorMsg('检查注册条件失败') if reg['error'] is not None: raise SisterErrorMsg(reg['error']) if reg['splash_index'] is None: reg['splash_index'] = current_app.config['INITIAL_SPLASH_INDEX'] cur.execute( ''' insert into users (user_token, unique_id, name, ring, splash_index, remarks, settings) values (%s, %s, %s, %s, %s, %s, '{}') ''', [ g.token, unique_id, info['name'], reg['ring'], reg['splash_index'], reg['remarks'] ]) flash('注册成功', 'success') raise SisterProceed()
def refresh(): cur = mysql.get_db().cursor() cur.execute( ''' update users set last_refresh=unix_timestamp() where uid=%s ''', [g.user.uid])
def _set_splash_index(uid, next_index): cur = mysql.get_db().cursor() cur.execute( ''' update users set splash_index=%s where uid=%s ''', [next_index, uid])
def delete_project(): """ INPUT: ids: list of int parent_id: int """ pids=list(set(request.json['ids'])) zid=int(request.json['parent_id']) p_o,_=g.user.projects(zid,need_list=False) p_o=p_o.get(zid,[]) p_onew=p_o[:] for pid in pids: if pid not in p_o: flash('类别不存在或没有权限','error') g.action_success=False return cur=mysql.get_db().cursor() for pid in pids: p_onew.remove(pid) # get linked extpid cur.execute(''' select extpid from projects where pid=%s ''',[pid]) extpid=cur.fetchone()[0] if extpid is None: # src project cur.execute(''' select count(*) from projects where extpid=%s ''',[pid]) peercnt=cur.fetchone()[0] if peercnt==0: # no peers: safely delete cur.execute(''' delete from projects where pid=%s and uid=%s ''',[pid,g.user.uid]) else: # otherwise: only remove from current user, delete it later cur.execute(''' update projects set uid=null, zid=null, share_hash=null where pid=%s and uid=%s ''',[pid,g.user.uid]) cur.execute(''' update tasks set uid=null where pid=%s and uid=%s ''',[pid,g.user.uid]) # completes will be deleted after last person deletes the project and tasks are deleted else: # imported project # delete self cur.execute(''' delete from projects where pid=%s and uid=%s ''',[pid,g.user.uid]) cur.execute(''' select count(*) from projects where (extpid=%s and pid!=%s) or /* other imported ones */ (pid=%s and uid is not null) /* original one that is not flagged as deletion */ ''',[extpid,pid,extpid]) peercnt=cur.fetchone()[0] if peercnt==0: # delete src cur.execute(''' delete from projects where pid=%s ''',[extpid]) model.update_linkedlist(p_o,p_onew,'projects')