Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1"""WSGI Flask-app for browsing a database. 

2 

3:: 

4 

5 +---------------------+ 

6 | layout.html | 

7 | +-----------------+ | +--------------+ 

8 | | search.html | | | layout.html | 

9 | | + | | | +---------+ | 

10 | | table.html ----------->| |row.html | | 

11 | | | | | +---------+ | 

12 | +-----------------+ | +--------------+ 

13 +---------------------+ 

14 

15You can launch Flask's local webserver like this:: 

16 

17 $ ase db abc.db -w 

18 

19or this:: 

20 

21 $ python3 -m ase.db.app abc.db 

22 

23""" 

24 

25import io 

26import sys 

27from pathlib import Path 

28 

29from ase.db import connect 

30from ase.db.core import Database 

31from ase.db.web import Session 

32from ase.db.project import DatabaseProject 

33 

34 

35class DBApp: 

36 root = Path(__file__).parent.parent.parent 

37 

38 def __init__(self): 

39 self.projects = {} 

40 

41 flask = new_app(self.projects) 

42 self.flask = flask 

43 

44 # Other projects will want to control the routing of the front 

45 # page, for which reasons we route it here in DBApp instead of 

46 # already in new_app(). 

47 @flask.route('/') 

48 def frontpage(): 

49 projectname = next(iter(self.projects)) 

50 return flask.view_functions['search'](projectname) 

51 

52 def add_project(self, name: str, db: Database) -> None: 

53 self.projects[name] = DatabaseProject.load_db_as_ase_project( 

54 name=name, database=db) 

55 

56 @classmethod 

57 def run_db(cls, db): 

58 app = cls() 

59 app.add_project('default', db) 

60 app.flask.run(host='0.0.0.0', debug=True) 

61 

62 

63def new_app(projects): 

64 from flask import Flask, render_template, request 

65 app = Flask(__name__, template_folder=str(DBApp.root)) 

66 

67 @app.route('/<project_name>') 

68 @app.route('/<project_name>/') 

69 def search(project_name: str): 

70 """Search page. 

71 

72 Contains input form for database query and a table result rows. 

73 """ 

74 if project_name == 'favicon.ico': 

75 return '', 204, [] # 204: "No content" 

76 session = Session(project_name) 

77 project = projects[project_name] 

78 return render_template(str(project.get_search_template()), 

79 q=request.args.get('query', ''), 

80 project=project, 

81 session_id=session.id) 

82 

83 @app.route('/update/<int:sid>/<what>/<x>/') 

84 def update(sid: int, what: str, x: str): 

85 """Update table of rows inside search page. 

86 

87 ``what`` must be one of: 

88 

89 * query: execute query in request.args (x not used) 

90 * limit: set number of rows to show to x 

91 * toggle: toggle column x 

92 * sort: sort after column x 

93 * page: show page x 

94 """ 

95 session = Session.get(sid) 

96 project = projects[session.project_name] 

97 session.update(what, x, request.args, project) 

98 table = session.create_table(project.database, 

99 project.uid_key, 

100 keys=list(project.key_descriptions)) 

101 return render_template(str(project.get_table_template()), 

102 table=table, 

103 project=project, 

104 session=session) 

105 

106 @app.route('/<project_name>/row/<uid>') 

107 def row(project_name: str, uid: str): 

108 """Show details for one database row.""" 

109 project = projects[project_name] 

110 row = project.uid_to_row(uid) 

111 dct = project.row_to_dict(row) 

112 return render_template(str(project.get_row_template()), 

113 dct=dct, row=row, project=project, uid=uid) 

114 

115 @app.route('/atoms/<project_name>/<int:id>/<type>') 

116 def atoms(project_name: str, id: int, type: str): 

117 """Return atomic structure as cif, xyz or json.""" 

118 row = projects[project_name].database.get(id=id) 

119 a = row.toatoms() 

120 if type == 'cif': 

121 b = io.BytesIO() 

122 a.pbc = True 

123 a.write(b, 'cif', wrap=False) 

124 return b.getvalue(), 200, [] 

125 

126 fd = io.StringIO() 

127 if type == 'xyz': 

128 a.write(fd, format='extxyz') 

129 elif type == 'json': 

130 con = connect(fd, type='json') 

131 con.write(row, 

132 data=row.get('data', {}), 

133 **row.get('key_value_pairs', {})) 

134 else: 

135 1 / 0 

136 

137 headers = [('Content-Disposition', 

138 'attachment; filename="{project_name}-{id}.{type}"' 

139 .format(project_name=project_name, id=id, type=type))] 

140 txt = fd.getvalue() 

141 return txt, 200, headers 

142 

143 @app.route('/gui/<int:id>') 

144 def gui(id: int): 

145 """Pop ud ase gui window.""" 

146 from ase.visualize import view 

147 # XXX so broken 

148 arbitrary_project = next(iter(projects)) 

149 atoms = projects[arbitrary_project].database.get_atoms(id) 

150 view(atoms) 

151 return '', 204, [] 

152 

153 @app.route('/test') 

154 def test(): 

155 return 'hello, world!' 

156 

157 @app.route('/robots.txt') 

158 def robots(): 

159 return ('User-agent: *\n' 

160 'Disallow: /\n' 

161 '\n' 

162 'User-agent: Baiduspider\n' 

163 'Disallow: /\n' 

164 '\n' 

165 'User-agent: SiteCheck-sitecrawl by Siteimprove.com\n' 

166 'Disallow: /\n', 

167 200) 

168 

169 return app 

170 

171 

172def main(): 

173 db = connect(sys.argv[1]) 

174 DBApp.run_db(db) 

175 

176 

177if __name__ == '__main__': 

178 main()