def __init__(self, src_conn_str, target_conn_str): self.src_conn_str = src_conn_str self.target_conn_str = target_conn_str self.src_db = Database(conn_str=src_conn_str) self.target_db = Database(conn_str=target_conn_str) self.state = None log.info('Copying data from "%s" to "%s" ...' % (src_conn, target_conn)) self._re_search = plugin_source.load_plugin('re_search') self._re_replace = plugin_source.load_plugin('re_replace') self._html2md = plugin_source.load_plugin('html2md')
def handle_rate_form(aid='', db=None): if db is None: db = Database.get_inst() ta = Database.DB_t_article if request.method == 'POST': fields = {} if not aid: aid = request.form.get('aid') if not aid: raise Exception('aid not provided.') row = request.form.get('row', default='') aweight = request.form.get('aweight') if aweight is not None: fields[ta.c.weight.name] = int(aweight) astatus = request.form.get('astatus') if astatus is not None: fields[ta.c.status.name] = int(astatus) if fields: stmt = ta.update().values(**fields).where(ta.c.id == aid) db.execute(stmt) return row
class CopyDB(object): base_path = os.path.dirname(os.path.dirname(__file__)) state_file_path = os.path.join(base_path, 'log', 'copy_db_state.json') def __init__(self, src_conn_str, target_conn_str): self.src_conn_str = src_conn_str self.target_conn_str = target_conn_str self.src_db = Database(conn_str=src_conn_str) self.target_db = Database(conn_str=target_conn_str) self.state = None log.info('Copying data from "%s" to "%s" ...' % (src_conn, target_conn)) self._re_search = plugin_source.load_plugin('re_search') self._re_replace = plugin_source.load_plugin('re_replace') self._html2md = plugin_source.load_plugin('html2md') def _fix_author(self, author): val = author if not val: return val if self._re_search: val = self._re_search.perform(val, "([^&]*)") # print(author, '==>>', val) return val def _fix_desc(self, val): if not val: return val if self._html2md: if not val.startswith(self._html2md.MD_PREFIX): if self._re_replace: val = self._re_replace.perform(val, "\\</?div.*?\\>", "") val = self._re_replace.perform(val, "\\<p.*?\\>.*\\</p\\>", "") val = self._re_replace.perform(val, "\\<a.*?\\>.*\\</a\\>", "") val = self._re_replace.perform(val, "[\\r|\\n]", "") val = self._re_replace.perform(val, "\\<br\\>", "\r\n") return val def _fix_content(self, val): if not val: return val if self._html2md: if not val.startswith(self._html2md.MD_PREFIX): if self._re_replace: val = self._re_replace.perform(val, "\\</?div.*?\\>", "") val = self._re_replace.perform(val, "\\<p.*?\\>.*\\</p\\>", "") val = self._re_replace.perform(val, "\\<a.*?\\>.*\\</a\\>", "") val = self._re_replace.perform(val, "[\\r|\\n]", "") val = self._re_replace.perform(val, "\\<br\\>", "\r\n") return val def _copy_table(self, t): assert self.state is not None, 'Must load state before start coping.' limit = 1000 if t.name not in self.state: self.state[t.name] = {'offset': 0} self.save_state() stat = self.state[t.name] offset = stat.get('offset', 0) done = stat.get('done', False) if done: log.info('Skip %s due to its done already (records:%d).' % (t.name, offset)) return while True: stmt = select(t.c).offset(offset).limit(limit) rs = self.src_db.execute(stmt) records = [] for r in rs: record = {c.name: r[c] for c in t.c} if 'author' in record: record['author'] = self._fix_author(record['author']) if 'desc' in record: record['desc'] = self._fix_desc(record['desc']) if 'content' in record: record['content'] = self._fix_content(record['content']) records.append(record) if records: self.target_db.conn.execute(t.insert(), records) offset += len(records) stat['offset'] = offset self.save_state() if len(records) < limit: break stat['done'] = True self.save_state() def copy_major_tables(self): self.target_db.meta.bind = self.target_db.engine self.target_db.meta.create_all() for t in self.target_db.meta.tables.values(): self._copy_table(t) def copy_site_chapter_tables(self): assert self.state is not None, 'Must load state before start coping.' stat_key = '_site_chapter_tables_' if stat_key not in self.state: self.state[stat_key] = {'offset': 0} self.save_state() stat = self.state[stat_key] offset = stat.get('offset', 0) done = stat.get('done', False) if done: log.info('Skip %s due to its done already (records:%d).' % (stat_key, offset)) return sites = sorted(SiteSchemas.keys()) i = 0 for site in sites: if i < offset: i += 1 continue tname = self.src_db.gen_site_chapter_table_name(site) t = self.src_db.get_db_t_site_chapter(tname) if self.src_db.exist_table(tname): if not self.target_db.exist_table(tname): t.create(bind=self.target_db.conn) self._copy_table(t) offset += 1 stat['offset'] = offset self.save_state() stat['done'] = True self.save_state() def copy_individual_chapter_tables(self): assert self.state is not None, 'Must load state before start coping.' ta = Database.DB_t_article chapter_tables_state_key = '_individual_chapter_tables_' if chapter_tables_state_key not in self.state: self.state[chapter_tables_state_key] = {'offset': 0} self.save_state() limit = 10 stat = self.state[chapter_tables_state_key] offset = stat.get('offset', 0) done = stat.get('done', False) if done: log.info('Skip %s due to its done already (records:%d).' % (chapter_tables_state_key, offset)) return self.src_db.meta.bind = self.target_db.engine while True: stmt = select([ta.c.id, ta.c.chapter_table]).where(ta.c.chapter_table != None) stmt = stmt.order_by(ta.c.id).offset(offset).limit(limit) rs = self.src_db.execute(stmt) count = 0 for r in rs: tname = r[ta.c.chapter_table] t = self.src_db.get_db_t_chapter(tname) if self.src_db.exist_table(tname): if not self.target_db.exist_table(tname): t.create(bind=self.target_db.conn) self._copy_table(t) count += 1 offset += 1 stat['offset'] = offset self.save_state() if count < limit: break stat['done'] = True self.save_state() def save_state(self): with open(self.state_file_path, 'wt', encoding='utf-8') as fp: json.dump(self.state, fp, indent=2, ensure_ascii=False) def read_state(self): try: with open(self.state_file_path, 'rt', encoding='utf-8') as fp: self.state = json.load(fp) log.info('State file found. Continue last copying.') except FileNotFoundError: log.info('No state file found. Start new copying.') self.state = {} self.save_state() def finish_state(self): suffix = datetime.now().strftime('%Y-%m-%d-%H-%M') fpath = os.path.split(self.state_file_path) fname = os.path.splitext(fpath[-1]) fname = fname[0] + '-' + suffix + fname[-1] fpath = os.path.join(fpath[0], fname) self.save_state() try: os.rename(self.state_file_path, fpath) except FileNotFoundError: pass
print('* DANGER! DANGER! DANGER !') print("* You are deleting ALL your database !!!") print('* All your data will be deleted and can NOT be recovered !!!') print("*" * 40) x = input('DANGER: Sure to delete all database !!! (y/n)') if x in ['y', 'Y']: db.meta.drop_all() print('Your database is dropped.') else: print('Cancelled.') if __name__ == '__main__': db = Database() db.meta.bind = db.engine if len(sys.argv) < 2: usage() exit(-1) command = sys.argv[1] if command == 'init': init(db) elif command == 'drop': drop(db) else: usage() exit(-1)
def render_article_index_page(site=None, iid=None): handle_client_ip() page, count_per_page, colspan, is_preview, toc, weight_from, weight_to, status_from, status_to, order1, order2, filters = get_request_query_args( ) context = {"encoding": 'utf-8'} db = Database() last_updated_row = handle_rate_form(db=db) try: ta = db.DB_t_article title = '' if site: title += SiteSchemas.get(site, {}).get(SSK.NAME, '') if iid: ti = db.DB_t_index stmt = select([ti.c.site + ' - ' + ti.c.text]) stmt = stmt.where(ti.c.id == iid) itext = db.conn.execute(stmt).scalar() or ('index %s' % iid) if title: title += ' - ' title += itext if not title: title = '全部小说' cellwidth = 100 / colspan i = 0 row = 0 odd = True sb = [] sb.append('<h3 align="center">%s</h3>' % title) render_pagination_and_filters(sb, return_link='/', return_text='返回首页') sb.append('<table align="center" width="95%" cellpadding="5">') stmt = select([ ta.c.id, ta.c.site, ta.c.name, ta.c.author, ta.c.category, ta.c.length, ta.c.status, ta.c.desc, ta.c.cover, ta.c.recommends, ta.c.favorites, ta.c.recommends_month, ta.c.done, ta.c.update_on, ta.c.chapter_table, ta.c.weight, ta.c.url, ta.c.timestamp ]) if site: stmt = stmt.where(ta.c.site == site) if iid: stmt = stmt.where(ta.c.iid == iid) stmt = stmt.where(between(ta.c.weight, weight_from, weight_to)) stmt = stmt.where(between(ta.c.status, status_from, status_to)) for fc, fo, fv in filters: if fc and fo and fv: stmt = stmt.where(text(fc + ' ' + fo + " '" + fv + "'")) stmt = stmt.order_by( text(order1[1:] + ' desc' if order1.startswith('-') else order1), text(order2[1:] + ' desc' if order2.startswith('-') else order2)) stmt = stmt.offset(count_per_page * page).limit(count_per_page) rs = db.conn.execute(stmt) for r in rs: if is_preview: chapter_table, tc, table_alone = db.get_chapter_table_name_def_alone( r) if chapter_table and db.exist_table(chapter_table): # get article toc stmt = select([tc.c.id, tc.c.name, tc.c.is_section ]) # .where(tc.c.content!=None) if not table_alone: stmt = stmt.where(tc.c.aid == r[ta.c.id]) stmt = stmt.order_by(tc.c.id).limit(toc) rs1 = db.conn.execute(stmt) else: rs1 = None if i % colspan == 0: sb.append('<tr id="r%s" style="%s;">' % (row, 'background-color:#eee' if odd else '')) odd = not odd row += 1 if is_preview: pass else: sb.append( '<td align="right"><img src="%s/%s/%s" width="%dpx" height="%dpx"></td>' % (album_url_path, site_for_url( r[ta.c.site]), r[ta.c.cover] or '', album_w, album_h)) sb.append('<td id="a%s" width="%d%%">' % (r[ta.c.id], cellwidth)) if is_preview: sb.append('<h4>%(id)s <a href="/%(id)s/">%(name)s</a></h4>' % r) sb.append('<div style="word-wrap:break-word;">%s</div>' % (r[ta.c.desc] or '')) render_rate_form(sb, aweight=r[ta.c.weight], astatus=r[ta.c.status], aid=r[ta.c.id], row=row - 1) sb.append( '<a href="/cache/%s/%s/%s?aid=%s">原目录页缓存</a>' % (base64.standard_b64encode(r[ta.c.site].encode()).decode(), base64.standard_b64encode(r[ta.c.url].encode()).decode(), Spiders.TOC, r[ta.c.id])) sb.append( ' | <a href="%s">原文</a>' % (SiteSchemas.get(r[ta.c.site], {}).get(SSK.URL.code, '') + r[ta.c.url])) if rs1: sb.append('<ul>') for r1 in rs1: sb.append('<li><a href="/%s/%s">%s</a></li>' % (r[ta.c.id], r1[tc.c.id], r1[tc.c.name])) rs1.close() del rs1 sb.append('</ul>') else: sb.append( '<h4>%(id)s <a title="%(desc)s" href="/%(id)s/">%(name)s</a></h4>' % r) sb.append( '<ul><li>作者: <a href="/author/%(author)s/">%(author)s</a></li>' % r) sb.append( '<li>类型: %(category)s</li><li>%(length)s 字</li><li>状态:%(status)s 完成:%(done)s</li>' % r) sb.append('<li>%s</li></ul></td>' % r[ta.c.update_on].strftime('%x')) for fc, fo, fv in filters: if fc: sb.append('<li>%s: %s</li>' % (fc, r[fc])) sb.append('</td>') i += 1 if i % colspan == 0: sb.append('</tr>') if rs.rowcount == 0: sb.append('<tr style="background-color:#eee"><td> 还未上传 </td></tr>') sb.append('</table>') render_pagination_and_filters(sb, return_link='/', return_text='返回首页') if last_updated_row: sb.append(''' <script> window.location = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search + '#r%s'; </script> ''' % last_updated_row) sb.append('<hr><center>-- Page %s END --</center>' % page) content = '\n'.join(sb) except Exception as err: content = '<p class="error">' + str(err) + '</p>' log.exception(err) context['content'] = content resp = make_response(render_template_string(template_page, **context)) resp.headers['Content-Type'] = 'text/html; charset=utf-8' return resp
def chapter(aid, cid): handle_client_ip() context = {"encoding": 'utf-8'} db = Database() ta = db.DB_t_article handle_rate_form(aid, db) try: # load article information stmt = select([ ta.c.site, ta.c.name, ta.c.weight, ta.c.status, ta.c.chapter_table ]).where(ta.c.id == aid) ra = db.conn.execute(stmt).fetchone() aname = ra[ta.c.name] chapter_table, tc, table_alone = db.get_chapter_table_name_def_alone( ra) # load article chapter stmt = select([tc.c.id, tc.c.name, tc.c.content, tc.c.is_section]).where(tc.c.id == cid) if not table_alone: stmt = stmt.where(tc.c.aid == aid) stmt = stmt.order_by(tc.c.id) r = db.conn.execute(stmt).fetchone() stmt = select([tc.c.id]).where( and_(tc.c.id < cid, tc.c.is_section == False)).order_by(tc.c.id.desc()).limit(1) prev = db.conn.execute(stmt).scalar() stmt = select([tc.c.id]).where( and_(tc.c.id > cid, tc.c.is_section == False)).order_by(tc.c.id).limit(1) nxt = db.conn.execute(stmt).scalar() prev = '/%s/%s/' % (aid, prev) if prev else '#' nxt = '/%s/%s/' % (aid, nxt) if nxt else '#' # nav = '<div align="center"><a href="{1}">上一章</a> <a href="/{0}">返回书页</a> <a href="{2}">下一章</a></div>'.format(aid, prev, nxt) nav = ' <a href="/{0}">返回书页</a> '.format(aid) if prev != '#': nav = '<div align="center"><a href="{0}">上一章</a>'.format( prev) + nav if nxt != '#': nav += '<a href="{0}">下一章</a></div>'.format(nxt) nav = '<div class="nav" align="center">%s</div>' % nav chapter_content = r[tc.c.content] if chapter_content: chapter_content = chapter_content.replace( '${STATIC}', '%s/%s' % (file_url_path, site_for_url(ra[ta.c.site]) or '_all')) if chapter_content.startswith(MD_PREFIX): chapter_content = markdown.markdown(chapter_content) else: chapter_content = chapter_content.replace('\r\n', '<br>') else: chapter_content = '还未上传' sb = [] sb.append('<h3 align="center">%s</h3>' % (r[tc.c.name] or aname)) sb.append('<center><p>快捷键:左、右键 翻页,-、=(+)键 字体大小</p></center>') sb.append(nav) render_rate_form(sb, aweight=ra[ta.c.weight], astatus=ra[ta.c.status]) sb.append('<div style="width:100%;background-color:#eee;">') sb.append('<hr />') sb.append( '<div id="content" class="article" style="font-size:22px">%s</div>\n' % chapter_content) sb.append('<hr />') sb.append('</div>') sb.append(nav) sb.append(''' <script language="javascript"> document.onkeydown = key_pressed; var prev_page="{0}"; var next_page="{1}"; var size = document.cookie ? document.cookie.split('=')[1] : document.all["content"].style['font-size']; size = parseInt(size); document.all["content"].style['font-size'] = size + 'px'; function key_pressed(event) {{ if (event.keyCode==37) location=prev_page; if (event.keyCode==39) location=next_page; if (event.keyCode==189 || event.keyCode == 109) {{ // size = parseInt(document.all["content"].style['font-size']); size -= 2; if (size <= 8) size = 8; }} if (event.keyCode==187 || event.keyCode == 107) {{ // size = parseInt(document.all["content"].style['font-size']);; size += 2; if (size >= 48) size = 68; }} if (event.keyCode==48) {{ size = 22; }} document.all["content"].style['font-size'] = size + 'px'; document.cookie = "size=" + size + "; path=/"; }} </script> '''.format(prev, nxt)) content = '\n'.join(sb) except Exception as err: log.exception(err) content = '<h1>Error</h1><p class="error">' + str(err) + '</p>' context['content'] = content resp = make_response(render_template_string(template_page, **context)) resp.headers['Content-Type'] = 'text/html; charset=utf-8' return resp
def modify_article(aid): handle_client_ip() context = {"encoding": 'utf-8'} db = Database() ta = db.DB_t_article colspan = 4 colwidth = 100 / colspan colw1 = 0.3 * colwidth colw2 = 0.7 * colwidth # bgcolors = ['#eee', 'azure', 'cornsik', 'aliceblue', 'lightgreen', 'khaki'] modifible_cols = [ ta.c.name, ta.c.author, ta.c.category, ta.c.length, ta.c.desc, ta.c.recommends, ta.c.favorites, ta.c.recommends_month ] # args col_name = request.form.get('c', default="", type=str) plugin_name = request.form.get('plug', default="", type=str) plugin_args = [] for i in range(3): plugin = request.form.get('pa' + str(i), default='', type=str) if plugin: plugin_args.append(plugin) try: # get article information stmt = select(ta.c).where(ta.c.id == aid).order_by(ta.c.id) rs = db.conn.execute(stmt) ra = rs.fetchone() # POST preview_cols = {} if request.method == 'POST': if col_name: if plugin_name: plugin = plugin_source.load_plugin(plugin_name) col_value_preview = plugin.perform(ra[col_name], *plugin_args) preview_cols[col_name] = col_value_preview # GET i = 0 odd = True sb = [] sb.append('<h3 align="center">修改 - %(name)s</h3>' % ra) sb.append('<p align="center"><a href="/i/%(iid)s">返回索引</a></p>\n' % ra) sb.append('<div align="center">') sb.append('<form method="POST">') # col plugin sb.append('<label>对字段') sb.append('<select name="c" style="width:100px" title="%s">' % col_name) sb.append('<option value=""></option>') for c in [] + modifible_cols: sb.append('<option value="{0}" {1}>{0}</option>'.format( c.name, 'selected' if c.name == col_name else '')) sb.append('</select>') sb.append('</label>') sb.append('<label>使用插件') sb.append('<select name="plug" style="width:100px" title="%s">' % plugin_name) sb.append('<option value=""></option>') for plugin in plugin_source.list_plugins(): sb.append('<option value="{0}" {1}>{0}</option>'.format( plugin, 'selected' if plugin == plugin_name else '')) sb.append('</select>') sb.append('</label>') # plugin args sb.append( '<button type="button" onclick="add_arg()">add arg</button><span id="plugin_args"></span>' ) sb.append("""<script> var args_container = document.getElementById("plugin_args"); var arg_count = 0; var plugin_args = {0}; function add_arg() {{ var label = document.createElement('label'); label.innerText = "参数" + arg_count; label.title = "NOTICE: NO CHECK. use as your own RISK!!!"; var input = document.createElement('input'); input.name = "pa" + arg_count; input.type = "text"; input.style = "width:70px"; input.value = plugin_args[arg_count] || ''; label.appendChild(input); args_container.appendChild(label); arg_count ++; }} for (var i=0; i<{1}; i++) {{ add_arg(); }} </script> """.format(plugin_args, len(plugin_args))) # for i in range(len(plugin_args)): # plugin_arg = plugin_args[i] # sb.append('<label title="NOTICE: NO CHECK. use as your own RISK!!!">参数%s:' % (i + 1)) # sb.append('<input name="pa%s" type="text" style="width:70px" value="%s"></label>' % (i, plugin_arg)) sb.append( '<button type="submit" style="background-color:yellow">Go</button>' ) sb.append('</form>') sb.append('</div>') sb.append('<hr>') # render columns i = 0 sb.append('<table align="center" width="95%">') for col in modifible_cols: if i % colspan == 0: sb.append('<tr colspan=%s>' % (colspan * 2)) odd = not odd bgcolor = (['#eee', 'white'] if odd else ['white', '#eee'])[i % 2] sb.append('<td style="background-color:%s" width="%s%%">%s</td>' % (bgcolor, colw1, col.name)) if col.name in preview_cols: sb.append( '<td style="background-color:%s;color:red;" width="%s%%">%s</td>' % (bgcolor, colw2, preview_cols[col.name])) else: sb.append( '<td style="background-color:%s" width="%s%%">%s</td>' % (bgcolor, colw2, ra[col])) i += 1 if i % colspan == 0: sb.append('</tr>') sb.append('</table>') rs.close() content = '\n'.join(sb) except Exception as err: content = '<h1>Error</h1><p class="error">' + str(err) + '</p>' context['content'] = content resp = make_response(render_template_string(template_page, **context)) resp.headers['Content-Type'] = 'text/html; charset=utf-8' return resp
def article_page(aid): # TODO: add button to fix 'desc' and 'chapter' content. e.g. markdown, regex replace, encoding, zip/unzip ... handle_client_ip() colspan = request.args.get('col', default=5, type=int) context = {"encoding": 'utf-8'} db = Database() ta = db.DB_t_article # POST handle_rate_form(aid, db) # GET try: # get article information stmt = select([ ta.c.id, ta.c.site, ta.c.iid, ta.c.name, ta.c.author, ta.c.category, ta.c.length, ta.c.status, ta.c.desc, ta.c.recommends, ta.c.favorites, ta.c.recommends_month, ta.c.update_on, ta.c.weight, ta.c.chapter_table, ta.c.timestamp ]).where(ta.c.id == aid).order_by(ta.c.id) rs = db.conn.execute(stmt) ra = rs.fetchone() chapter_table, tc, table_alone = db.get_chapter_table_name_def_alone( ra) if chapter_table and db.exist_table(chapter_table): # get article toc stmt = select([tc.c.id, tc.c.name, tc.c.is_section]) #.where(tc.c.content!=None) if not table_alone: stmt = stmt.where(tc.c.aid == aid) stmt = stmt.order_by(tc.c.id) rs = db.conn.execute(stmt) else: rs = None i = 0 odd = True sb = [] sb.append('<h3 align="center">%(name)s</h3>' % ra) sb.append('<p align="center"><a href="/i/%(iid)s">返回索引</a></p>\n' % ra) render_rate_form(sb, aweight=ra[ta.c.weight], astatus=ra[ta.c.status]) sb.append('<table align="center" width="95%">') if rs: for r in rs: if i % colspan == 0: sb.append('<tr style="%s">' % 'background-color:#eee' if odd else '') odd = not odd if r['is_section']: sb.append( '</tr><tr style="background-color:silver"><td colspan="%s" align="center">%s</td></tr>' % (colspan, r['name'] or '')) i = 0 else: sb.append('<td><a href="/%s/%s/">%s</a></td>' % (aid, r['id'], r['name'] or '阅读')) i += 1 if i % colspan == 0: sb.append('</tr>') if rs is None or rs.rowcount == 0: sb.append('<tr style="background-color:#eee"><td> 还未上传 </td></tr>') sb.append('</table>') content = '\n'.join(sb) except Exception as err: content = '<h1>Error</h1><p class="error">' + str(err) + '</p>' context['content'] = content resp = make_response(render_template_string(template_page, **context)) resp.headers['Content-Type'] = 'text/html; charset=utf-8' return resp
def home(): handle_client_ip() context = {"encoding": "utf-8"} db = Database() colspan = request.args.get('col', default=6, type=int) i = 0 odd = False sb = [] site_schemas = SiteSchemas try: sb.append('<h3 align="center">文库 - moltspider</h3>') ti = db.DB_t_index # sites list stmt = select([ ti.c.site, func.max(ti.c.update_on).label(ti.c.update_on.name), func.count(1).label('count') ]).group_by(ti.c.site) rs = db.conn.execute(stmt) sb.append('<h4 align="center">支持的网站 - <a href="/all">全部</a></h4>') sb.append( '<ul style="list-style:none" align="left" width="95%" cellpadding="5">' ) for r in rs: site = r[ti.c.site] site_name = site_schemas.get(site, {}).get(SSK.NAME, site) sb.append( '<li style="display:inline;"><a href="/s/%s">%s</a> (%s类)' % (site, site_name, r['count'])) sb.append(' %s</li> | ' % r[ti.c.update_on.name].strftime("%x")) sb.append('</ul>') # indexes per site stmt = select([ti.c.id, ti.c.site, ti.c.text, ti.c.update_on]).order_by(ti.c.site, ti.c.id) rs = db.conn.execute(stmt) sb.append('<table align="center" width="90%" cellpadding="5">') site = '' site_name = '' line_style = '' for r in rs: if r[ti.c.site] != site: line_style = 'background-color:#eee' if odd else '' odd = not odd i = 0 site = r[ti.c.site] site_name = site_schemas.get(site, {}).get(SSK.NAME, site) sb.append('<tr style="%s">' % line_style) sb.append( '<td id="%s" colspan="%s" align="center"><h4>%s</h4></td></tr>' % (site, colspan, site_name)) if i % colspan == 0: sb.append('<tr style="%s;">' % 'background-color:#eee' if odd else '') line_style = 'background-color:#eee' if odd else '' odd = not odd sb.append('<td><a href="/i/%(id)s">%(text)s</a></td>' % r) sb.append('</td>') i += 1 if i % colspan == 0: sb.append('</tr>') sb.append('</table>') content = '\n'.join(sb) except Exception as err: content = '<h2>Error</h2><br><p class="error">' + str(err) + '</p>' log.exception(err) context['content'] = content resp = make_response(render_template_string(template_page, **context)) resp.headers['Content-Type'] = 'text/html; charset=utf-8' return resp