diff --git a/ricgraph_explorer/ricgraph_explorer.py b/ricgraph_explorer/ricgraph_explorer.py index 15db83d..2fa05dd 100644 --- a/ricgraph_explorer/ricgraph_explorer.py +++ b/ricgraph_explorer/ricgraph_explorer.py @@ -31,14 +31,28 @@ # This file is Ricgraph explorer, a web based tool to access nodes in # Ricgraph. # The purpose is to illustrate how web based access using Flask can be done. -# To keep it simple, everything has been done in this file. It can be improved -# in many ways, but I didn't do that, since it is meant for research purposes, +# To keep it simple, everything has been done in this file. +# Please note that this code is meant for research purposes, # not for production use. That means, this code has not been hardened for # "the outside world". Be careful if you expose it to the outside world. # Original version Rik D.T. Janssen, January 2023. # Extended Rik D.T. Janssen, February 2023. # # ######################################################################## +# +# For table sorting. Ricgraph explorer uses sorttable.js. +# It is copied from https://www.kryogenix.org/code/browser/sorttable +# on February 1, 2023. At that link, you'll find a how-to. It is licensed under X11-MIT. +# It is renamed to ricgraph_sorttable.js since it has a small modification +# related to case-insensitive sorting. The script is included in html_body_end. +# +# ############################################################################## +# +# Ricgraph explorer uses W3.CSS, a modern, responsive, mobile first CSS framework. +# See https://www.w3schools.com/w3css/default.asp. +# +# ############################################################################## + import urllib.parse from typing import Union @@ -50,54 +64,112 @@ ricgraph_explorer = Flask(__name__) # When we do a query, we return at most this number of nodes. -MAX_RESULTS = 75 +MAX_RESULTS = 50 + +# The style for the buttons, note the space before and after the text. +button_style = ' w3-button uu-yellow w3-round-large w3-mobile ' +# A button with a black line around it. +button_style_border = button_style + ' w3-border rj-border-black ' # The html stylesheet. stylesheet = '' +# The html preamble +html_preamble = '' +html_preamble += '' +html_preamble += '' + +# The html page header. +page_header = '
' +page_header += '
' +page_header += '
' +page_header += '' +page_header += ' Ricgraph explorer' +page_header += '
' +page_header += 'Home' +page_header += 'Exact match search' +page_header += 'String search' +page_header += '
' +page_header += '
' + # The html page footer. -footer = '' -# For table sorting. sorttable.js is copied from https://www.kryogenix.org/code/browser/sorttable -# on February 1, 2023. At that link, you'll find a how-to. It is licensed under X11-MIT. -# It is renamed to ricgraph_sorttable.js since it has a small modification. -footer += '' - -# The html search form. -search_form = '
' -search_form += '

This is Ricgraph explorer.' -search_form += '

Type something to search (this is an case-sensitive, exact match ' -search_form += 'search, using AND if you use multiple fields):' -search_form += '
' -search_form += '
' -search_form += '
' -search_form += '

' -search_form += '

' - -searchcontains_form = '
' -searchcontains_form += '

This is Ricgraph explorer.' -searchcontains_form += '

Type something to search (this is a case-insensitive, inexact match):' -searchcontains_form += '
' -searchcontains_form += '

' -searchcontains_form += '

' +page_footer = '' + +# The first part of the html page, up to stylesheet and page_header. +html_body_start = '' +html_body_start += '' +html_body_start += html_preamble +html_body_start += 'Ricgraph explorer' +html_body_start += '' +html_body_start += stylesheet +html_body_start += page_header + +# The last part of the html page, from page_footer to script inclusion. +html_body_end = page_footer +html_body_end += '' +html_body_end += '' +html_body_end += '' + +# The html search form for an exact match search (on /search). +search_form = '
' +search_form += '
' +search_form += '
' +search_form += '

Type something to search

' +search_form += 'This is an case-sensitive, exact match search, using AND if you use multiple fields:' +search_form += '
' +search_form += '
' +search_form += '
' +search_form += '' +search_form += '
' +search_form += '
' +search_form += '
' +search_form += '
' +search_form += '
' +search_form += '
' +search_form += '
' + +# The html search form for a search on a string (on /searchcontains). +searchcontains_form = '
' +searchcontains_form += '
' +searchcontains_form += '
' +searchcontains_form += '

Type something to search

' +searchcontains_form += 'This is a case-insensitive, inexact match:' +searchcontains_form += '
' +searchcontains_form += '
' +searchcontains_form += '
' +searchcontains_form += '' +searchcontains_form += '
' +searchcontains_form += '
' +searchcontains_form += '
' +searchcontains_form += '
' +searchcontains_form += '
' # ############################################################################## @@ -109,18 +181,20 @@ def index_html() -> str: :return: html to be rendered. """ - global stylesheet, footer + global html_body_start, html_body_end - html = stylesheet - html += '

This is Ricgraph explorer. You can use it to explore Ricgraph.' + html = html_body_start + html += get_html_for_cardstart() + html += 'This is Ricgraph explorer. You can use it to explore Ricgraph.' html += '

' - html += footer + html += get_html_for_cardend() + html += html_body_end return html @@ -136,9 +210,9 @@ def search(id_value=None) -> str: :param id_value: value to search for in the 'value' field. :return: html to be rendered. """ - global stylesheet, footer, search_form + global html_body_start, html_body_end, search_form - html = stylesheet + html = html_body_start if request.method == 'POST': search_name = request.form['search_name'] search_category = request.form['search_category'] @@ -157,7 +231,7 @@ def search(id_value=None) -> str: html += find_nodes_in_ricgraph(name='', category='', value=escape(id_value)) - html += footer + html += html_body_end return html @@ -170,9 +244,9 @@ def searchcontains() -> str: :return: html to be rendered. """ - global stylesheet, footer, searchcontains_form + global html_body_start, html_body_end, searchcontains_form - html = stylesheet + html = html_body_start if request.method == 'POST': search_value = request.form['search_value'] html += find_nodes_in_ricgraph(value=escape(search_value), @@ -180,7 +254,7 @@ def searchcontains() -> str: else: html += searchcontains_form - html += footer + html += html_body_end return html @@ -189,7 +263,7 @@ def searchcontains() -> str: # ############################################################################## def faceted_navigation_in_ricgraph(nodes: list, name: str = '', category: str = '', value: str = '') -> str: - """Do faceted navigation in Ricgraph. + """Do facet navigation in Ricgraph. The facets will be constructed based on 'name' and 'category'. Facets chosen will be "catched" in function search(). If there is only one facet (for either one or both), it will not be shown. @@ -198,7 +272,8 @@ def faceted_navigation_in_ricgraph(nodes: list, :param name: name of the nodes to find. :param category: category of the nodes to find. :param value: value of the nodes to find. - :return: html to be rendered. + :return: html to be rendered, or empty string ('') if faceted navigation is + not useful because there is only one facet. """ if len(nodes) == 0: return '' @@ -217,42 +292,58 @@ def faceted_navigation_in_ricgraph(nodes: list, category_histogram[node['category']] += 1 if len(name_histogram) <= 1 and len(category_histogram) <= 1: - # Faceted navigation does not make sense, don't show facets. + # We have only one facet, so don't show the facet panel. return '' - faceted_form = '
' + faceted_form = get_html_for_cardstart() + faceted_form += '
' + faceted_form += '' if len(name_histogram) == 1: - # Get the first (and only) element in the dict, we pass it as hidden field to search(). + # Get the first (and only) element in the dict, pass it as hidden field to search(). name_key = str(list(name_histogram.keys())[0]) faceted_form += '' else: - faceted_form += '
Select for faceted navigation on "name"' + faceted_form += '
' + faceted_form += '
' + faceted_form += 'Faceted navigation on "name"' + faceted_form += '
' + faceted_form += '
' # Sort a dict on value: # https://stackoverflow.com/questions/613183/how-do-i-sort-a-dictionary-by-value for bucket in sorted(name_histogram, key=name_histogram.get, reverse=True): - name_label = bucket + ' (' + str(name_histogram[bucket]) + ') ' - faceted_form += '' - faceted_form += '' - faceted_form += '
' + name_label = bucket + ' (' + str(name_histogram[bucket]) + ')' + faceted_form += '' + faceted_form += '
' + faceted_form += '
' + faceted_form += '

' if len(category_histogram) == 1: - # Get the first (and only) element in the dict, we pass it as hidden field to search(). + # Get the first (and only) element in the dict, pass it as hidden field to search(). category_key = str(list(category_histogram.keys())[0]) faceted_form += '' else: - faceted_form += '
Select for faceted navigation on "category":' + faceted_form += '
' + faceted_form += '
' + faceted_form += 'Faceted navigation on "category"' + faceted_form += '
' + faceted_form += '
' for bucket in sorted(category_histogram, key=category_histogram.get, reverse=True): - category_label = bucket + ' (' + str(category_histogram[bucket]) + ') ' - faceted_form += '' - faceted_form += '' - faceted_form += '

' - - # Send name, category and value as hidden field to search(). + category_label = bucket + ' (' + str(category_histogram[bucket]) + ')' + faceted_form += '' + faceted_form += '
' + faceted_form += '' + faceted_form += '
' + + # Send name, category and value as hidden fields to search(). faceted_form += '' faceted_form += '' faceted_form += '' - faceted_form += '' - faceted_form += '' + faceted_form += '' + faceted_form += '' + faceted_form += '' + faceted_form += get_html_for_cardend() html = faceted_form return html @@ -284,8 +375,10 @@ def find_nodes_in_ricgraph(name: str = '', category: str = '', value: str = '', return 'Ricgraph could not be opened.' if name == '' and category == '' and value == '': - html = '

Please enter a value in the search field.' - html += '
' + 'Try again' + '.' + html = get_html_for_cardstart() + html += 'The search field should have a value.' + html += '
' + 'Please try again' + '.' + html += get_html_for_cardend() return html if name_want is None: @@ -295,8 +388,10 @@ def find_nodes_in_ricgraph(name: str = '', category: str = '', value: str = '', if use_contain_phrase: if len(value) < 3: - html = '

Please use at least three characters for your search string.' - html += '
' + 'Try again' + '.' + html = get_html_for_cardstart() + html += 'The search string should be at least three characters.' + html += '
' + 'Please try again' + '.' + html += get_html_for_cardend() return html result = rcg.read_all_nodes_containing(value=value) @@ -304,109 +399,132 @@ def find_nodes_in_ricgraph(name: str = '', category: str = '', value: str = '', result = rcg.read_all_nodes(name=name, category=category, value=value) if len(result) == 0: - html = '

Ricgraph explorer could not find anything.' - html += '
' + 'Try again' + '.' + html = get_html_for_cardstart() + html += 'Ricgraph explorer could not find anything.' + html += '
' + 'Please try again' + '.' + html += get_html_for_cardend() return html - html = '

You searched for: