diff --git a/src/2023/2023-10-30-tests-for-migration-for-merging-solutions.ipynb b/src/2023/2023-10-30-tests-for-migration-for-merging-solutions.ipynb new file mode 100644 index 0000000..433d6e6 --- /dev/null +++ b/src/2023/2023-10-30-tests-for-migration-for-merging-solutions.ipynb @@ -0,0 +1,265 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 40, + "id": "157389d2", + "metadata": {}, + "outputs": [], + "source": [ + "from sqlalchemy import create_engine\n", + "from sqlalchemy import text\n", + "\n", + "#engine_remote = create_engine(\n", + "# f\"mysql+mysqlconnector://serlo:{PASSWORD}@localhost:7777/serlo?charset=utf8mb4\"\n", + "#)\n", + "\n", + "engine_local = create_engine(\n", + " f\"mysql+mysqlconnector://root:secret@localhost:3306/serlo?charset=latin1\"\n", + ")\n", + "\n", + "class MySQLSession:\n", + " def __init__(self, engine):\n", + " self.engine = engine\n", + " \n", + " def __enter__(self):\n", + " self.connection = self.engine.connect()\n", + " return self\n", + " \n", + " def __exit__(self, *args):\n", + " self.connection.commit()\n", + " self.connection.close()\n", + " \n", + " def execute(self, statement, **kwargs):\n", + " return self.connection.execute(text(statement), kwargs)\n", + "\n", + " def query(self, statement, **kwargs):\n", + " return list(self.execute(statement, **kwargs))\n", + " \n", + " def begin(self):\n", + " return self.connection.begin()\n", + " \n", + "with MySQLSession(engine_local) as session:\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b68146a7", + "metadata": {}, + "outputs": [], + "source": [ + "EXAMPLE_ENTITY_ID=54210" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "e643d031", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[54227]\n" + ] + } + ], + "source": [ + "def select_first(row):\n", + " assert len(row) == 1\n", + "\n", + " return row[0]\n", + "\n", + "def select_first_elements(elements):\n", + " return [select_first(e) for e in elements]\n", + "\n", + "def get_children(session, entity_id, child_type):\n", + " return select_first_elements(session.query(\"\"\"\n", + " select child_id\n", + " from entity_link\n", + " join entity child on child.id = entity_link.child_id\n", + " join type child_type on child_type.id = child.type_id\n", + " where parent_id = :parent_id and child_type.name = :child_type\n", + " \"\"\", parent_id=entity_id, child_type=child_type))\n", + "\n", + "with MySQLSession(engine_local) as session:\n", + " print(get_children(session, EXAMPLE_ENTITY_ID, \"text-solution\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "70ca37d1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "182709\n" + ] + } + ], + "source": [ + "def get_current_revision(session, entity_id):\n", + " result = session.query(\"\"\"\n", + " select current_revision_id from entity where id = :entity_id\n", + " \"\"\", entity_id = entity_id)\n", + " \n", + " return select_first(select_first(result))\n", + "\n", + "with MySQLSession(engine_local) as session:\n", + " print(get_current_revision(session, EXAMPLE_ENTITY_ID))" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "d01103d5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'plugin': 'exercise', 'state': {'content': {'plugin': 'rows', 'state': [{'plugin': 'text', 'state': [{'type': 'p', 'children': [{'text': 'Ordne folgendem Graphen die richtige Funktionsgleichung zu:'}]}]}, {'plugin': 'image', 'state': {'src': 'https://assets.serlo.org/legacy/56e92eda3e891_00804a4cae5eaff522ae3b59cf859b06ca6a47b0.png', 'alt': 'Graph', 'caption': {'plugin': 'text', 'state': [{'type': 'p', 'children': [{}]}]}}}]}, 'interactive': {'plugin': 'scMcExercise', 'state': {'isSingleChoice': True, 'answers': [{'content': {'plugin': 'text', 'state': [{'type': 'p', 'children': [{'text': ''}, {'type': 'math', 'src': 'f(x)=4\\\\cdot\\\\sin(x)', 'inline': True, 'children': [{'text': 'f(x)=4\\\\cdot\\\\sin(x)'}]}, {'text': ''}]}]}, 'isCorrect': True, 'feedback': {'plugin': 'text', 'state': [{'type': 'p', 'children': [{'text': 'Richtig! Der Nobelpreis ist ganz nah ;-)'}]}]}}, {'content': {'plugin': 'text', 'state': [{'type': 'p', 'children': [{'text': ''}, {'type': 'math', 'src': 'f(x)=4\\\\cdot\\\\cos(x)', 'inline': True, 'children': [{'text': 'f(x)=4\\\\cdot\\\\cos(x)'}]}, {'text': ''}]}]}, 'isCorrect': False, 'feedback': {'plugin': 'text', 'state': [{'type': 'p', 'children': [{'text': 'Leider falsch! Du denkst wahrscheinlich schon in die richtige Richtung, aber schaue dir noch einmal die Unterschiede der '}, {'type': 'a', 'href': '/1909', 'children': [{'text': 'trigonometrischen Funktionen'}]}, {'text': ' an.'}]}]}}, {'content': {'plugin': 'text', 'state': [{'type': 'p', 'children': [{'text': ''}, {'type': 'math', 'src': 'f(x)=5\\\\cdot\\\\cos(x)', 'inline': True, 'children': [{'text': 'f(x)=5\\\\cdot\\\\cos(x)'}]}, {'text': ''}]}]}, 'isCorrect': False, 'feedback': {'plugin': 'text', 'state': [{'type': 'p', 'children': [{'text': 'Da solltest du noch einmal nachdenken. Die Funktionsgleichung hat leider so gar nichts mit dem Graphen zu tun!'}]}]}}, {'content': {'plugin': 'text', 'state': [{'type': 'p', 'children': [{'text': ''}, {'type': 'math', 'src': 'f(x)=12\\\\cdot\\\\sin(x)', 'inline': True, 'children': [{'text': 'f(x)=12\\\\cdot\\\\sin(x)'}]}, {'text': ''}]}]}, 'isCorrect': False, 'feedback': {'plugin': 'text', 'state': [{'type': 'p', 'children': [{'text': 'Leider falsch! Du hast mit der Sinus-Funktion schon den richtigen Riecher. Allerdings ist mal '}, {'type': 'math', 'src': '12', 'inline': True, 'children': [{'text': '12'}]}, {'text': ' falsch. Schau mal, wie weit die Kurve auf der y-Achse ausschlägt.'}]}]}}]}}}}\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "def get_content(session, revision_id):\n", + " result = session.query(\"\"\"\n", + " select value from entity_revision_field where entity_revision_id = :revision_id\n", + " and field=\"content\"\n", + " \"\"\", revision_id=revision_id)\n", + " \n", + " return json.loads(select_first(select_first(result)))\n", + "\n", + "with MySQLSession(engine_local) as session:\n", + " print(get_content(session, 182709))" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "8608cb13", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "182709\n" + ] + } + ], + "source": [ + "def get_license_id(session, entity_id):\n", + " result = session.query(\"\"\"\n", + " select license_id from entity where id = :entity_id\n", + " \"\"\", entity_id = entity_id)\n", + " \n", + " return select_first(select_first(result))\n", + "\n", + "with MySQLSession(engine_local) as session:\n", + " print(get_current_revision(session, EXAMPLE_ENTITY_ID))" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "93e2b106", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "updated: 182709\n" + ] + } + ], + "source": [ + "has_state = lambda x: \"state\" in x and isinstance(x[\"state\"], dict)\n", + "\n", + "def update_exercise(session, exercise_id):\n", + " solution_id = select_first(get_children(session, exercise_id, \"text-solution\"))\n", + " \n", + " exercise_revision_id = get_current_revision(session, exercise_id)\n", + " solution_revision_id = get_current_revision(session, solution_id)\n", + " \n", + " exercise_content = get_content(session, exercise_revision_id)\n", + " solution_content = get_content(session, solution_revision_id) \n", + " \n", + " exercise_license_id = get_license_id(session, exercise_id)\n", + " solution_license_id = get_license_id(session, solution_id)\n", + " \n", + " assert has_state(solution_content)\n", + " \n", + " if solution_license_id != exercise_license_id:\n", + " solution_content[\"state\"][\"licenseId\"] = solution_license_id\n", + " \n", + " assert has_state(exercise_content) and exercise_content[\"plugin\"] == \"exercise\"\n", + " \n", + " if \"solution\" not in exercise_content[\"state\"]:\n", + " exercise_content[\"state\"][\"solution\"] = solution_content\n", + " \n", + " session.execute(\"\"\"\n", + " update entity_revision_field set value = :content\n", + " where entity_revision_id = :revision_id\n", + " and field=\"content\" \n", + " \"\"\", revision_id = exercise_revision_id, content=json.dumps(exercise_content, indent=2))\n", + " \n", + " print(\"updated: \", exercise_revision_id)\n", + " \n", + "with MySQLSession(engine_local) as session:\n", + " update_exercise(session, 54210)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "aa53ac4d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "updated: 170171\n" + ] + } + ], + "source": [ + "with MySQLSession(engine_local) as session:\n", + " update_exercise(session, 10129)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}