Creating a recursive sitemap with relative href links

I use Flask to open a local directory of HTML files on a web page.

I also use jinja2 to create a sitemap in the lefthand div my main endpoint.

I could not correctly specify the endpoint URL of my subfolders.

As mentioned in the code below, how would I dynamically build a relative link from /docs (i.e. /docs/folder1/subfolder1/SubFolder1Page.html )? The way I am currently setting the value for href is obviously not working.

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Docs Demo</title> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> </head> <body> <div id="container"> <div class="left_frame"> <h1>{{ tree.name }}</h1> <ul> {%- for item in tree.children recursive %} <!-- How would I build a relative link from /docs/ ie /docs/folder1/subfolder1/SubFolder1Page.html --> <li><a href="docs/{{ item.name }}" target="iframe1">{{ item.name }} {%- if item.children -%} <ul>{{ loop(item.children) }}</ul> {%- endif %}</a></li> {%- endfor %} </ul> </div> <div class="right_frame"> <iframe name="iframe1"></iframe> </div> </div> </body> </html> 

Example folder structure:

demo structure

How it looks in general, displaying the contents of file1.html : sitemap view

+5
source share
3 answers

So, I realized that I was able to solve my problem.

I managed to get this very functional result: Docs demo checkbox

Please note that my template is only good for files with the .html extension, although it can be easily extended to support other file extensions.

Here is my finalized templates\template.html file:

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Docs Demo</title> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> </head> <body> <div id="container"> <div class="left_frame"> <h1>{{ tree.name }}</h1> <ul> {%- for item in tree.children recursive %} {% if '.html' in item.name %} <li><a href="docs/{{ item.name }}" target="iframe1"> {{ item.name.split('/')[-1:][0] }} {%- if item.children -%} <ul>{{ loop(item.children) }}</ul> {%- endif %}</a></li> {% else %} <li>{{ item.name }} {%- if item.children -%} <ul>{{ loop(item.children) }}</ul> {%- endif %}</li> {% endif %} {%- endfor %} </ul> </div> <div class="right_frame"> <iframe name="iframe1"></iframe> </div> </div> </body> </html> 

You can refer to King Reload 's answer for an analysis of what I changed in the template.html file to make this work correctly.

And here is the demo_app.py script that serves my HTML HTML documents via Flask:

 import threading import os import webbrowser from flask import Flask, render_template, send_from_directory app = Flask(__name__, static_folder='static') ROOT = os.path.dirname(os.path.abspath(__file__)) DOCS_ROOT = os.path.join(app.static_folder, 'docs') @app.route('/') def docs_tree(): return render_template('template.html', tree=make_tree(DOCS_ROOT)) @app.route('/docs/<path:filename>') def send_docs(filename): return send_from_directory(directory=DOCS_ROOT, 'docs'), filename=filename) def make_tree(path): tree = dict(name=os.path.basename(path), children=[]) try: lst = os.listdir(path) except OSError: pass # ignore errors else: for name in lst: fn = os.path.join(path, name) if os.path.isdir(fn): tree['children'].append(make_tree(fn)) else: np = os.path.join(path.replace(DOCS_ROOT, ''), name).replace('\\', '/') if np.startswith('/'): np = np[1:] tree['children'].append(dict(name=np)) return tree if __name__ == '__main__': host = 'localhost' port = '8888' url = 'http://{h}:{p}'.format(h=host, p=port) threading.Timer(3, lambda: webbrowser.open(url)).start() app.run(host=host, port=port, debug=False) 

The most noticeable changes to demo_app.py after demo_app.py my original question were as follows:

  • After initializing the app I set DOCS_ROOT using app.static_folder ;
  • In the send_docs() function, I changed the send_from_directory() directory argument to use DOCS_ROOT ;
  • Inside make_tree() inside the else block of the for loop, I added:

     np = os.path.join(path.replace(DOCS_ROOT, ''), name).replace('\\', '/') if np.startswith('/'): np = np[1:] 

    All this makes the absolute path name , removes what matches DOCS_ROOT , leaving only the relative path (and then replacing \\ for / ), which leads to a simple relative path from static/docs . If the relative path starts with a / , I delete it (since there is a final / from docs in template.html .


For anyone interested in the simplified style sheet ( static\styles.css ), I used (along with some updated improvements):

 html { min-height:100%; position:relative; } body { overflow:hidden; } .container { width:100%; overflow:auto; } .left_frame { float:left; background:#E8F1F5; width:25%; height:100vh; } .right_frame { float:right; background:#FAFAFA; width:75%; height:100vh; } .right_frame iframe { display:block; width:100%; height:100%; border:none; } 
+5
source

To add @ HEADLESS_0NE to the solution:

He added some more if to the for loop , for example:

  {%- for item in tree.children recursive %} -> {% if '.html' in item.name %} <li><a href="docs/{{ item.name }}" target="iframe1"> -> {{ item.name.split('/')[-1:][0] }} {%- if item.children -%} <ul>{{ loop(item.children) }}</ul> {%- endif %}</a></li> -> {% else %} -> <li>{{ item.name }} -> {%- if item.children -%} -> <ul>{{ loop(item.children) }}</ul> -> {%- endif %}</li> -> {% endif %} {%- endfor %} 

Everything with -> was changed in html , I could not find what he added in his python or css , but in short:

  • An if to check if .html in item.name .
  • The division on item.name , therefore / is removed.
  • An else if item.name does not have .html .

This basically adds ul and li in the correct format.

For a more detailed explanation, I hope HEADLESS_0NE can provide us with more information on what it could change in a python script.

+3
source

With a flask I built a site map using the following

 from flask import url_for def get_flask_resources(): verbs = ["POST","GET","PUT","DELETE"] resources = {} for rule in app.url_map.iter_rules(): if has_no_empty_params(rule): resource = url_for(rule.endpoint, **(rule.defaults or {})) if resource not in resources: resources[resource] = {} for verb in verbs: if verb in rule.methods: resources[resource][verb] = { 'function':rule.endpoint, 'docs':app.view_functions[rule.endpoint].__doc__ } else: resource = rule.rule if resource not in resources: resources[resource] = {} for verb in verbs: if verb in rule.methods: resources[resource][verb] = { 'function':rule.endpoint, 'docs':app.view_functions[rule.endpoint].__doc__ } return resources 

This function returns a dictionary like this

 { "/endpoint1": { "GET": { "docs": "", "function": "endpoint1" } }, "/endpoint2": { "GET": { "docs": "", "function": "endpoint2" } }, "/endpoint1/something": { "POST": { "docs": "", "function": "endpoint1_something" } }, } 

I had an endpoint returning this data and then formatted at the front end. Dictionary keys are the URIs that you would like to use in links.

This assumes that a flask route will be configured for each HTML document, which may not be the case.

One of the advantages of using this is that it is dynamic if you add more HTML documents / jar routes.

+2
source

Source: https://habr.com/ru/post/1268369/


All Articles