From 3e34b1c8c183fa3946cb4fc6085f78b516f64562 Mon Sep 17 00:00:00 2001 From: Sebastian David <sebastian.david@uni-hamburg.de> Date: Sat, 4 Dec 2021 22:16:37 +0100 Subject: [PATCH] Changed some variable names, removed some redundancies and expanded code documentation --- citation_parser_ui.py | 147 ++++++++++++++++++++++------------- Info Box.txt => info_box.txt | 2 +- 2 files changed, 95 insertions(+), 54 deletions(-) rename Info Box.txt => info_box.txt (95%) diff --git a/citation_parser_ui.py b/citation_parser_ui.py index 48de29d..fd7ee84 100644 --- a/citation_parser_ui.py +++ b/citation_parser_ui.py @@ -11,8 +11,14 @@ import input.publication app = dash.Dash(__name__) +# List of options when inputting data and generating the graph additional_options = ['Update Automatically','Smart Input'] +# Reads the contents of info_box.txt. They can later be displayed by pressing the corresponding button. +f = open('info_box.txt', 'r') +boxcontent = f.read() +f.close() + app.layout = html.Div([ # Layer 0: For the Header and Help Function(s) html.Div([ @@ -22,15 +28,17 @@ app.layout = html.Div([ # Layer 1: For all mandatory Inputs html.Div([ "Input: ", + # A simple box for inputting a string. Value is transmitted upon pressen return or clicking out of the box. dcc.Input(id='input-string', value='', type='text',debounce=True), + # Forward recursion. Values between 1 and 10 can be entered. dcc.Input(id='forward-depth',value='1',type='number',min='1',max='10'), + # Backward recursion. Values between 1 and 10 can be entered. dcc.Input(id='backward-depth',value='1',type='number',min='1',max='10'), + # Upload box. Can be used via drag-and-drop or byclicking on it to open a file viewer. dcc.Upload( id="upload-data", children=html.Div( - ["Drag and drop or click to select a file to upload."] - ), - + ["Drag and drop or click to select a file to upload."]), style={ "width": "30%", "height": "60px", @@ -44,15 +52,21 @@ app.layout = html.Div([ ]), # Layer 2: For the checklist, Remove-/Start-Buttons and input-error-message html.Div([ + # All input DOIs are collected in this checklist. It is initialized to avoid error messages. dcc.Checklist(id='input-checklist',options=[],labelStyle = dict(display='block'),value=[]), + # Displays error message if 'Smart Input' is active. html.Div(id='input-err',style={'color':'red'}), + # Clears the entire list. html.Button(id='clear-all-button',children='Clear All'), + # Clear all selected elements. html.Button(id='clear-selected-button',children='Clear Selected'), + # Starts the process that generates a graph. html.Button(id='start-button',children='Generate Graph') ]), # Layer 3: For additional Options (e.g. Topological Sort) html.Div([ html.H4('Additional Options'), + # A checklist of all additional options that are listed above. dcc.Checklist(id='additional-options', options=[{'label':k,'value':k} for k in additional_options], value=[]) @@ -63,10 +77,6 @@ app.layout = html.Div([ ]) ]) -''' -Most important callback function. Updates the checklist that holds all inputs. -input-string is required as Output to clear the input box after each input -''' @app.callback( Output('input-checklist','options'), Output('input-checklist','value'), @@ -82,49 +92,78 @@ input-string is required as Output to clear the input box after each input ) def update_input_checklist(input_value,btn1,btn2,filecontents,all_inputs,selected_inputs,additional_options): ''' - :param input_value: given by dcc.Input - :type input_value: string - :param btn1: signals pressing of clear-all-button - :param btn2: signals pressing of clear-selected-button - :param all_inputs: all labels and values from the checklist, - regardless if they have been checked or not - :type all_inputs: list of dictionaries with 2 entries each - :param selected_inputs: values of all checked elements - :type selected_inputs: list of strings + Most important callback function. Updates the checklist that holds all inputs. + State of the checklist as input is needed so that previews entries are readded. + input-string is required as Output to clear the input box after each input. + Different actions are performed depending on which input triggered the callback. + The value-attribute of input-checklist must be updates so that the values + of deleted elements no longer appear in the list of selected elements. + + :param input_value: given by dcc.Input + :type input_value: string + :param btn1: signals pressing of clear-all-button + :type btn1: int + :param btn2: signals pressing of clear-selected-button + :type btn2: int + :param filecontents: the contents of an uploaded file + :type filecontents: bit-string + :param all_inputs: all labels and values from the checklist, + regardless if they have been checked or not + :type all_inputs: list of dictionaries with 2 entries each + :param selected_inputs: values of all checked elements + :type selected_inputs: list of strings + :param addtitional_options: all checked additional options + :type additional_options: list of strings ''' + # changed_id is used to determine which Input has triggered the callback changed_id = [p['prop_id'] for p in callback_context.triggered][0] + # if clear-all-button was pressed: if 'clear-all-button' in changed_id: return list(),list(),'','' + # if clear-selected-button was pressed: if 'clear-selected-button' in changed_id: all_inputs = [i for i in all_inputs if i['value'] not in selected_inputs] return all_inputs,list(),'','' + # when a new element is added via dcc.Input if 'input-string' in changed_id: - options = all_inputs - currValues = [x['value'] for x in options] + # Creates a list of previously added inputs to make sure nothing is added twice + currValues = [x['value'] for x in all_inputs] if input_value not in currValues: + + # if 'Smart Input' is selected, the input will be checked for validity + # and a more readable string will be returned if 'Smart Input' in additional_options: try: + # Attempts to call get_publication. If unsuccesful, + # the DOI is not added and an error message is returned i = InputInterface() pub = i.get_publication(input_value) except Exception as err: return options,selected_inputs,'','{}'.format(err) + # Creates a more readable string to display in the checklist rep_str = pub.contributors[0] + ',' + pub.journal + ',' + pub.publication_date - options.append({'label':rep_str, 'value':input_value}) + all_inputs.append({'label':rep_str, 'value':input_value}) + + # if 'Smart Input' is not selected, the input value is added as is, + # without checking for validity. else: - options.append({'label':input_value,'value':input_value}) - return options,selected_inputs,'','' + all_inputs.append({'label':input_value,'value':input_value}) + return all_inputs,selected_inputs,'','' + # when a txt-file is uploaded if 'upload-data.contents' in changed_id: if filecontents: - string = filecontents - found = base64.b64decode(re.search(',(.+?)$', string).group(1)) - filecontents = found.decode('utf-8') - list_of_inputs = (filecontents.strip().split('\n')) - options = all_inputs - CurrValues = [x['value'] for x in options] + # Skips the info portion that is added when a file is uploaded + found = base64.b64decode(re.search(',(.+?)$', filecontents).group(1)) + # Returns the binary string into a proper text + text = found.decode('utf-8') + # Creates a list of inputs by splitting the lines + list_of_inputs = (text.strip().split('\n')) + CurrValues = [x['value'] for x in all_inputs] + # For every line the same actions as for a single input are performed for input_value in list_of_inputs: if input_value not in CurrValues: if 'Smart Input' in additional_options: @@ -132,36 +171,35 @@ def update_input_checklist(input_value,btn1,btn2,filecontents,all_inputs,selecte i = InputInterface() pub = i.get_publication(input_value) except Exception as err: - return options,selected_inputs,'','{}'.format(err) + return all_inputs,selected_inputs,'','{}'.format(err) rep_str = pub.contributors[0] + ',' + pub.journal + ',' + pub.publication_date - options.append({'label':rep_str, 'value':input_value}) + all_inputs.append({'label':rep_str, 'value':input_value}) else: - options.append({'label':input_value,'value':input_value}) - return options,selected_inputs,'','' + all_inputs.append({'label':input_value,'value':input_value}) + return all_inputs,selected_inputs,'','' # when the programm is first started: + # if this is not done, the input_checklist will be generated + # with one element that contains an empty string if input_value == '': app.layout['input-checklist'].options.clear() return list(),list(),'','' -''' -This callback shows and hides the (first) help-box -''' @app.callback( Output('info-box','children'), Input('show-info','n_clicks') ) def show_hide_info_box(n_clicks): + ''' + This callback shows and hides the (first) info-box by, checking how often + the button has been pressed. The text was loaded at the top. + :param n_clicks: number of times show-info has been clicked. + 'type n_clicks: int + ''' if n_clicks % 2 == 0: return '' else: - f = open('Info Box.txt', 'r') - boxcontent = f.read() return html.Div(boxcontent, style={'whiteSpace': 'pre-line'}) - f.close() -''' -Basic structure for a callback that generates an output -''' @app.callback( Output('test-output','children'), Input('start-button','n_clicks'), @@ -174,19 +212,22 @@ Basic structure for a callback that generates an output def generate_output(n_clicks,all_inputs,selected_inputs, forward_depth,backward_depth,additional_options): ''' - :param n_clicks: how often has Generate Graph been clicked - :type n_clicks: int - :param all_inputs: all labels and values from the checklist, - regardless if they have been checked or not - :type all_inputs: list of dictionaries with 2 entries each - :param selected_inputs: values of all checked elements - :type selected_inputs: list of strings - :param forward_depth: forward recursion depth - :type forward_depth: unsigned int - :param backward_depth: backward recursion depth - :type backward_depth: unsigned int - :param additional_options: value of all selected additional options - :type additional_options: list of strings + Basic structure for a callback that generates an output. This is only a + proof of concept and has noting to do with the intended output yet. + + :param n_clicks: how often has Generate Graph been clicked + :type n_clicks: int + :param all_inputs: all labels and values from the checklist, + regardless if they have been checked or not + :type all_inputs: list of dictionaries with 2 entries each + :param selected_inputs: values of all checked elements + :type selected_inputs: list of strings + :param forward_depth: forward recursion depth + :type forward_depth: unsigned int + :param backward_depth: backward recursion depth + :type backward_depth: unsigned int + :param additional_options: value of all selected additional options + :type additional_options: list of strings ''' changed_id = [p['prop_id'] for p in callback_context.triggered][0] if n_clicks is None: diff --git a/Info Box.txt b/info_box.txt similarity index 95% rename from Info Box.txt rename to info_box.txt index b03d2f4..3cb826b 100644 --- a/Info Box.txt +++ b/info_box.txt @@ -14,7 +14,7 @@ Clear Selected: clearing all selected inputs Generate Graph: generates the graph -Update Automatically: updates Automatically the graph for every new input +Update Automatically: automatically updates the graph for every new input Smart Input: checks the correctness of the entered DOI and shows a nicer depiction: Author, Journal, publication date. -- GitLab