From dba3f3076de12b531290624ca292cd5f3cc1af9d Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 29 May 2024 20:04:32 +0200 Subject: [PATCH] SinglePages work now, DB Tables are created and can be written --- SLAEForms Testing.code-workspace | 10 + slaeforms/app.py | 366 ++++++++++++++++++--- slaeforms/defaul_wip.json | 21 ++ slaeforms/default.json | 47 +-- slaeforms/dicttablecode.py | 15 +- slaeforms/templates/all_links.html | 9 + slaeforms/templates/standard_template.html | 59 ++++ slaeforms/templates/tempaltetest1.html | 0 slaeforms/templates/test_page0.html | 18 + slaeforms/templates/test_page1.html | 23 ++ slaeforms/templates/teststartpage.html | 21 ++ 11 files changed, 503 insertions(+), 86 deletions(-) create mode 100644 SLAEForms Testing.code-workspace create mode 100644 slaeforms/defaul_wip.json create mode 100644 slaeforms/templates/standard_template.html create mode 100644 slaeforms/templates/tempaltetest1.html create mode 100644 slaeforms/templates/test_page0.html create mode 100644 slaeforms/templates/test_page1.html create mode 100644 slaeforms/templates/teststartpage.html diff --git a/SLAEForms Testing.code-workspace b/SLAEForms Testing.code-workspace new file mode 100644 index 0000000..4901684 --- /dev/null +++ b/SLAEForms Testing.code-workspace @@ -0,0 +1,10 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "liveServer.settings.multiRootWorkspaceName": "CSS Testing" + } +} \ No newline at end of file diff --git a/slaeforms/app.py b/slaeforms/app.py index 76a5d49..09a5a9b 100644 --- a/slaeforms/app.py +++ b/slaeforms/app.py @@ -40,12 +40,12 @@ app.secret_key = b"29fe9e8edd407c5491d4f1c05632d9fa33e26ed8734a3f5e080ebac3772a5 #---------testing JSON Stuff #open the json file with the config -configfile = open("singleformconfig.json") +testconfigfile = open("singleformconfig.json") #convert it to dict -config = json.load(configfile) -configfile.close() +testconfig = json.load(testconfigfile) +testconfigfile.close() # get the questions: Questions is a list that contains the keys of the dictionary -questions = list(config) +questions = list(testconfig) @@ -57,21 +57,21 @@ questions = list(config) # create the model for the response table class Response(db.Model): - id = db.Column(db.UUID(as_uuid=True), primary_key=True, nullable=False) - user_id = db.Column(db.UUID(as_uuid=True), nullable=False) - question_title = db.Column(db.String(30)) - likert_result = db.Column(db.Integer, nullable=False) - notes = db.Column(db.String(200)) - date_created = db.Column(db.DateTime) + id = db.Column("id",db.UUID(as_uuid=True), primary_key=True, nullable=False) + user_id = db.Column("user_id",db.UUID(as_uuid=True), nullable=False) + question_title = db.Column("question_title",db.String(30)) + likert_result = db.Column("likert_result",db.Integer, nullable=False) + notes = db.Column("notes",db.String(200)) + date_created = db.Column("date_created",db.DateTime) def __repr__(self) -> str: return "" % self.id # This table is always created class User(db.Model): - user_id = db.Column(db.UUID(as_uuid=True), primary_key=True, nullable=False) - device_id = db.Column(db.UUID(as_uuid=True), nullable=False) - question_order = db.Column(db.String(60)) - date_created = db.Column(db.DateTime, default=datetime.today()) # todo test if this default works + user_id = db.Column("user_id",db.UUID(as_uuid=True), primary_key=True, nullable=False) + device_id = db.Column("device_id",db.UUID(as_uuid=True), nullable=False) + question_order = db.Column("question_order",db.String(60)) + date_created = db.Column("date_created",db.DateTime, default=datetime.today()) # todo test if this default works def __repr__(self) -> str: return "" % self.user_id @@ -84,9 +84,239 @@ except SQLAlchemyError as e: -# Actual main code for Form etc ----------------------------------------------------- +# -------Testing the actual final form from json------------------------------------------------ -@app.route("/video", methods=["GET", "POST"]) + + + + + +#open the json file with the config +configfile = open("default.json") #todo replace with other name +#convert it to dict +config = json.load(configfile) +configfile.close() +db_tables = {} # contains all dynamically created tables, key = table/classname + + +#TODO insert code to create all tables +def create_json_tables(): + print(config) + for block_key, block_content in config.items(): + if "database_table" in block_content: + print("debug") + if not (block_content["database_table"]["table_name"] in db_tables): + print("New table: \n {table}".format(table=block_content["database_table"]["table_name"])) + db_tables[block_content["database_table"]["table_name"]]=create_model_class(block_content["database_table"]) + print("created table: {table}".format(table=db_tables[block_content["database_table"]["table_name"]])) + + print("tables in db_tables: \n {db_tables}".format(db_tables=db_tables)) + +def create_model_class(schema): + class_name = schema["table_name"].capitalize() + print("creating table class: {class_name}".format(class_name=class_name)) + + # Define class attributes dynamically + attributes = {"__tablename__": schema["table_name"]} + + # id as key and date as standard fields + attributes["id"] = Column("id",db.UUID(as_uuid=True), primary_key=True, nullable=False) + attributes["user_id"] = Column("user_id",db.UUID(as_uuid=True), primary_key=True, nullable=False) + attributes["date_created"] = db.Column("date_created",db.DateTime) + + for column_name, column_info in schema["fields"].items(): + if column_info["type"] == "integer": + column_type = Integer + elif column_info["type"] == "string": + column_type = String(int(column_info["size"])) + + attributes[column_name] = Column(column_name,column_type, nullable=column_info["nullable"]) + + print("attributes of the table: ",attributes) + # Create the model class + return type(class_name, (db.Model,), attributes) + + + +create_json_tables() +# create the table (existing tables are not overwritten) +try: + print("try to create tables") + with app.app_context(): + db.create_all() + print("successfully created all tables") +except SQLAlchemyError as e: + print("Error occurred during database creation:", str(e)) + +@app.route("/teststart", methods=["GET", "POST"]) +def teststartpage(): + if not "slaeform_device_id" in session: + # If this device was not seen, remember it. + new_device_id = uuid.uuid4() + session["slaeform_device_id"] = new_device_id + session["agreed_to_tos"] = False + + + if request.method == "POST": + #right now if a user that has an active session goes to the startpage again and accepts tos + #it will just start another session and discard the previous session + + #config is the dict with the info + #get the block names + block_names = config.keys() + session["block_names"] = list(block_names) + session["block_order"] = {} # only for templates, for each block the list of keys for stimuli + session["current_block_index"] = 0 + session["current_stimulus_index"] = 0 + session["current_block_name"] = session["block_names"][session["current_block_index"]] + session["number_of_blocks"] = len(session["block_names"]) + + print("number of blocks: ",len(session["block_names"])) + + print("Startpage post") + print(session["block_names"]) + + for name in block_names: + if config[name]["type"] == "TaskTemplate": + match config[name]["stimulus"]["type"]: + case "single_video": + order = list(config[name]["stimulus"]["list"]) # order = list of simuli keys + if config[name]["stimulus"]["order"] == "random": + random.shuffle(order[name]) #in random order + session["block_order"][name] = order + + #save the new user to the database and the session + session["agreed_to_tos"] = True + new_user_id = uuid.uuid4() + session["slaeform_user_id"] = new_user_id + device_id = session["slaeform_device_id"] + user_id = new_user_id + question_order = str(session["block_order"]) + date = datetime.today() + new_user = User(user_id=user_id, device_id=device_id,question_order=question_order,date_created = date) #,question_order=question_order + + db.session.add(new_user) + db.session.commit() + + print("block order: {order}".format(order=session["block_order"])) + try: + db.session.add(new_user) + db.session.commit() + + except: + return "There was a problem while adding the user to the Database" + return redirect("/jsonform") + + return render_template( + "teststartpage.html" + ) + + +@app.route("/jsonform") +def jsonform(): + #user is not yet registered and should not be here + if not "slaeform_user_id" in session: + return redirect(url_for(teststartpage)) #TODO replace this later with actual startpage + + + current_block = config[session["current_block_name"]] + print("jsonform") + print("current_block_name: {current_block_name}".format(current_block_name=session["current_block_name"])) + print("current_block_index: {current_block_order}".format(current_block_order=session["current_block_index"])) + print("current_stimulus: {current_stimulus}".format(current_stimulus=session["current_stimulus_index"])) + + #print("current Blockname: {blockname}, current block index: {blockindex}, current stim index: {stimulusindex}".format(blockname=session["current_block_name"], + # blockindex=session["current_block_index"], + # stimulusindex=session["current_stimulus_index"] ) ) + + # erster Fall: SinglePage + if current_block["type"] == "SinglePage": + + return render_template(current_block["template"]) + + + # ansonsten, templates: + current_block_order = session["block_order"][current_block] + current_block_stimuli = config[current_block]["stimuli"] + current_stimulus = current_block_order[0] + stimulus_type=current_stimulus["type"] + stimulus_configuration = config[current_block]["stimulus"]["configuration"] + + + if current_block[type] == "TaskTemplate": + print("case: TaskTemplate") + match stimulus_type: + case "single_video": + stimulus_configuration["video_url"] = config[current_block]["stimuli"]["list"] + + return render_template( + "standard_template.html", + stimuli=current_block_stimuli, + stimulus_type=stimulus_type, + current_stimulus=current_stimulus, + stimulus_configuration=stimulus_configuration + ) + + + + + return "Error, none of the Blocks triggered" + + +@app.route("/send_json", methods=["POST"]) +def sendpage_json(): + print("send_json") + # Do I need to write to a table at all? + # I can figure it out by checking if the current block has a database field, that is best + if not ("database_table" in config[session["current_block_name"]]): #it has no database field, so nothing to receive + # so just move on + print("no database table") + update_session() + return redirect("/jsonform") + + # now to if it has a database field + + # find out which table we need to write to + table_name = config[session["current_block_name"]]["database_table"]["table_name"] + print("Form posted: {rqform}".format(rqform=request.form)) + print("Writing to table: {table_name}".format(table_name=table_name)) + session_user_id = session["slaeform_user_id"] + new_id = uuid.uuid4() + date = datetime.today() + + new_entry = db_tables[table_name](id=new_id,user_id = session_user_id,date_created = date) + + for key, value in request.form.items(): + print("hasattr key: ", key) + if hasattr(new_entry, key): + print("key exists: ", key) + setattr(new_entry, key, value) + print("setattr value: ", value) + print("entry: ", new_entry) + try: + db.session.add(new_entry) + db.session.commit() + except Exception as e: + print("Error occurred: {e}".format(e=str(e))) + return "There was a problem while adding the response to the Database" + + # Now move to the next stimulus or block + update_session() + + return redirect("/jsonform") + +def update_session(): + #if session["current_stimulus_index"] < session["current_max_stimulus_index"] + # session["current_stimulus_index"] += 1 #TODO do this properly + if(session["current_block_index"] < session["number_of_blocks"]-1): + session["current_block_index"] += 1 + + session["current_block_name"] = session["block_names"][session["current_block_index"]] + + +# Actual main code for Form etc -------------------------------------------------------------- + +@app.route("/video") def videopage(): return render_template( @@ -123,7 +353,8 @@ def sendpage(): return "There was a problem while adding the response to the Database" - +#TODO this is bugged rn, it will skip to the next question when refreshed. +#Move the removal of the first element to the send function to fix this @app.route("/form", methods=["GET", "POST"]) def formpage(): #user is not yet registered and should not be here @@ -144,19 +375,14 @@ def formpage(): print(current_question) print("the new sessionorder rn:") print(session["question_order"]) - session["question_order"] = session["question_order"] - print("has this changed sth?:") - print(session["question_order"]) - - return render_template( "layout2.html", - config=config, + config=testconfig, current_question = current_question, - videotype=config[current_question]["type"], - video_url= config[current_question]["video1"], - blocks = config[current_question]["blocks"] + videotype=testconfig[current_question]["type"], + video_url= testconfig[current_question]["video1"], + blocks = testconfig[current_question]["blocks"] ) @@ -207,19 +433,33 @@ def startpage(): ) -# Database stuff------------------------------------ + + + + + +# Database stuff------------------------------------------------------------------------------ # the contents of all tables @app.route("/table_contents") def table_contents(): - tables = db.metadata.tables.keys() + meta = db.metadata + #meta.reflect(db.engine) # Uncomment this to also get the hidden tables, but this crashes rn + tables = meta.tables.keys() table_contents = {} + #print(tables) for table_name in tables: - table = db.metadata.tables[table_name] + table = meta.tables[table_name] columns = table.columns.keys() - rows = db.session.query(table).all() - + #print(table) + rows = db.session.query(table) + try: + rows = rows.all() + except Exception as e: + print("Error occurred: {e}".format(e=str(e))) + print("Values:\n Table = {table}\n Tabletype = {tabletype}\n".format(table=table,tabletype=type(table))) + table_contents[table_name] = { 'columns': columns, 'rows': rows @@ -230,26 +470,13 @@ def table_contents(): table_contents=table_contents, ) -# Route to delete all entries -@app.route('/delete_all_entries', methods=['GET']) -def delete_all_entries(): - try: - meta = db.metadata - for table in reversed(meta.sorted_tables): # Iterate through tables in reverse order to handle foreign key constraints - db.session.execute(table.delete()) - db.session.commit() - return 'All entries deleted successfully' - except Exception as e: - # Rollback changes if any error occurs - db.session.rollback() - return f'Error occurred: {str(e)}', 500 - finally: - # Close the session - db.session.close() + @app.route('/show_tables') def show_tables(): - tables = db.metadata.tables + meta = db.metadata + meta.reflect(db.engine) + tables = meta.tables return render_template('show_tables.html', tables=tables) # Root page ----------------------------- @@ -270,9 +497,48 @@ def all_links(): links.append((url, rule.endpoint)) return render_template("all_links.html", links=links) +# delete all tables as last link -------------------------- + +# Route to delete all entries +@app.route('/delete_json_tables', methods=['GET']) +def delete_json_tables(): + try: + meta = db.metadata + meta.reflect(db.engine) + for table in reversed(meta.sorted_tables): # Iterate through tables in reverse order to handle foreign key constraints + if table.name != "user" and table.name != "response": + print("Deleting Table: {name}".format(name=table.name)) + db.session.execute(table.delete()) + + db.session.commit() + return 'All entries deleted successfully' + except Exception as e: + # Rollback changes if any error occurs + db.session.rollback() + return f'Error occurred: {str(e)}', 500 + finally: + # Close the session + db.session.close() +# Route to delete all entries +@app.route('/delete_all_entries', methods=['GET']) +def delete_all_entries(): + # here I could also use a "drop_all()", that works jsut like create all from the creation part + # this together with the reflect could drop actually all tables + try: + meta = db.metadata + for table in reversed(meta.sorted_tables): # Iterate through tables in reverse order to handle foreign key constraints + db.session.execute(table.delete()) + db.session.commit() + return 'All entries deleted successfully' + except Exception as e: + # Rollback changes if any error occurs + db.session.rollback() + return f'Error occurred: {str(e)}', 500 + finally: + # Close the session + db.session.close() if __name__ == '__main__': - # Create database tables - db.create_all() \ No newline at end of file + app.run(debug=True) \ No newline at end of file diff --git a/slaeforms/defaul_wip.json b/slaeforms/defaul_wip.json new file mode 100644 index 0000000..5d4b74f --- /dev/null +++ b/slaeforms/defaul_wip.json @@ -0,0 +1,21 @@ +{ + "Block_1":{ + "type": "SinglePage", + "template": "startpage.html", + "database_table" :{ + "TableName": "Datenschutzerklaerung", + "Fields": { + } + } + }, + "Block 2":{ + "type": "TaskTemplate", + "tempalte": "tempaltetest1.html", + "name" : "Block2Responses", + "database_table": { + "TableName": "Datenschutzerklaerung", + "Fields": { + } + } + } +} \ No newline at end of file diff --git a/slaeforms/default.json b/slaeforms/default.json index 7857a3b..bf25a70 100644 --- a/slaeforms/default.json +++ b/slaeforms/default.json @@ -1,43 +1,20 @@ { "Block_1":{ "type": "SinglePage", - "template": "startpage.html", + "template": "test_page0.html" + }, + "Block_2":{ + "type": "SinglePage", + "template": "test_page1.html", "database_table" :{ - "TableName": "Datenschutzerklärung", - "Fields": { - } - } - }, - "Block 2":{ - "type": "TaskTemplate", - "tempalte": "tempaltetest1.html", - "name" : "Block2Responses", - "databasetable": { - "question_title" : { - "type":"likert" - } - } - }, - "question 3":{ - "type": "single", - "video1": "https://www.youtube-nocookie.com/embed/XTMIomsXxKM?si=r2zB6OKERH6Jdpi6", - "scales": { - "block1":{ - "type": "likert", - "numberofpoints": "3", - "points":{ - "point1": "left", - "point2": "none", - "point3": "right" + "table_name": "Datenschutzerklaerung", + "fields": { + "accepted":{ + "type": "string", + "size": "7", + "nullable": "false" } - }, - "block2":{ - "type": "textinput", - "length": "200" - }, - "block3":{ - "type": "video" } } - } + }, } \ No newline at end of file diff --git a/slaeforms/dicttablecode.py b/slaeforms/dicttablecode.py index e8a8372..6824e8e 100644 --- a/slaeforms/dicttablecode.py +++ b/slaeforms/dicttablecode.py @@ -117,4 +117,17 @@ def customsend(): return "There was a problem while adding the response to the Database" -# End testing------------------------------------- \ No newline at end of file +# End testing------------------------------------- + + + +# create the model for the response table +class Response(db.Model): + id = db.Column(db.UUID(as_uuid=True), primary_key=True, nullable=False) + user_id = db.Column(db.UUID(as_uuid=True), nullable=False) + question_title = db.Column(db.String(30)) + likert_result = db.Column(db.Integer, nullable=False) + notes = db.Column(db.String(200)) + date_created = db.Column(db.DateTime) + def __repr__(self) -> str: + return "" % self.id \ No newline at end of file diff --git a/slaeforms/templates/all_links.html b/slaeforms/templates/all_links.html index 421e630..9476363 100644 --- a/slaeforms/templates/all_links.html +++ b/slaeforms/templates/all_links.html @@ -10,9 +10,18 @@ +

All available pages

    +

    --------------------------------------------

    {% for url, endpoint in links %} + {% if endpoint == "delete_all_entries" %} +

    -

    +

    -

    +

    -

    +

    - make sure you really want this -

    + {% endif %}
  • {{ endpoint }}
  • +

    --------------------------------------------

    {% endfor %}
diff --git a/slaeforms/templates/standard_template.html b/slaeforms/templates/standard_template.html new file mode 100644 index 0000000..71a07b1 --- /dev/null +++ b/slaeforms/templates/standard_template.html @@ -0,0 +1,59 @@ + + + + + + + Testform + + + + + +

Stimulus part

+ {% if (stimulus_type == "single_video") %} + {{ single_video(**stimulus_configuration) }} + {% elif (False) %} + + {% else %} +

Error: Block {{ stimulus["type"] }} could not be loaded!

+ {% endif %} + + + +

Questions

+
+ {% for question in questions %} + {% if (questions[question] == "likert") %} + + {% elif (config["question 1"]["blocks"][block]["type"] == "textinput") %} + {% else %} +

Error: Block {{config["question 1"]["blocks"][block]["type"]}} could not be loaded!

+ {% endif %} + {% endfor %} +

+ + + + + +{% macro single_video(video_url, embed="yt", title="",width="560", height="315", class="center", code=

No code given

) -%} + {% if (embed == "yt") %} +
+ {% if (title != "") %} +

{{title}}

+ {% endif %} + +
+ {% else %} + {{code}} + {% endif %} +{%- endmacro %} + +{% macro input(name, value='', type='text', size=20) -%} + +{%- endmacro %} \ No newline at end of file diff --git a/slaeforms/templates/tempaltetest1.html b/slaeforms/templates/tempaltetest1.html new file mode 100644 index 0000000..e69de29 diff --git a/slaeforms/templates/test_page0.html b/slaeforms/templates/test_page0.html new file mode 100644 index 0000000..dbabed6 --- /dev/null +++ b/slaeforms/templates/test_page0.html @@ -0,0 +1,18 @@ + + + + + + + Test Page 0 - Nothing + + + + +

This is just a test page for the single page option of the json configuration, but without something to submit

+
+ +

+
+ + \ No newline at end of file diff --git a/slaeforms/templates/test_page1.html b/slaeforms/templates/test_page1.html new file mode 100644 index 0000000..3dc2f94 --- /dev/null +++ b/slaeforms/templates/test_page1.html @@ -0,0 +1,23 @@ + + + + + + + Test Page 1 - Datenschutzerklaerung + + + + +

This is just a test page for the single page option of the json configuration

+
+ + +

+
+ + \ No newline at end of file diff --git a/slaeforms/templates/teststartpage.html b/slaeforms/templates/teststartpage.html new file mode 100644 index 0000000..8fecf26 --- /dev/null +++ b/slaeforms/templates/teststartpage.html @@ -0,0 +1,21 @@ + + + + + + + DGS Avatar Study + + + +

Hello! Thank you for participating in our study!

+
+ + +

+
+ + + \ No newline at end of file