diff --git a/examples/01_users.ipynb b/examples/01_users.ipynb new file mode 100644 index 00000000..71d082cb --- /dev/null +++ b/examples/01_users.ipynb @@ -0,0 +1,502 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "ngTJ3YbHlb8s" + }, + "source": [ + "# Mergin Maps Users Management" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "q0eLjSMzlwdx" + }, + "source": [ + "The Mergin Maps client allows you to manage your workspace users, their roles, and project-specific permissions. Every user has a workspace role and a project role. A project role is defined for a specific project and overrides the workspace role for that project. For more details, see the [permissions documentation](https://dev.merginmaps.com/docs/manage/permissions/).\n", + "\n", + "See the [API reference](https://merginmaps.com/docs/dev/integration/) for more details about methods used in this notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "IKmFEjG-mmL6" + }, + "source": [ + "First let's install mergin maps client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "I_vpP6NnmqV7" + }, + "outputs": [], + "source": [ + "!pip install mergin-client" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "u05lxbRQm2VF" + }, + "source": [ + "Login to Mergin Maps using your workspace user with `Owner` permission." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "executionInfo": { + "elapsed": 1822, + "status": "ok", + "timestamp": 1748364457967, + "user": { + "displayName": "Fernando Ribeiro", + "userId": "15488710231554262191" + }, + "user_tz": -60 + }, + "id": "dWQorVqZnRNl", + "outputId": "778487c5-b0b5-4a7f-a024-33122e49b6fb", + "trusted": true + }, + "outputs": [], + "source": [ + "# Use here your login username and password\n", + "LOGIN = \"...\"\n", + "PASSWORD = \"...\"\n", + "\n", + "import mergin\n", + "\n", + "client = mergin.MerginClient(login=LOGIN, password=PASSWORD)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gFN3jXIjntwf" + }, + "source": [ + "Let's create a workspace and project as base for our users management example.\n", + "\n", + "Set the `WORKSPACE` variable to your desired workspace name and the `PROJECT` variable to the name of the project created in your workspace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "executionInfo": { + "elapsed": 745, + "status": "ok", + "timestamp": 1748364795430, + "user": { + "displayName": "Fernando Ribeiro", + "userId": "15488710231554262191" + }, + "user_tz": -60 + }, + "id": "27rA4VfgoJjy" + }, + "outputs": [], + "source": [ + "# Add here your existing workspace name and the new project name\n", + "WORKSPACE = \"...\"\n", + "PROJECT = \"...\"\n", + "\n", + "# Create new workspace\n", + "# INFO: Only uncomment if you are able to create a new workspace. Mergin Maps free tier only allows for 1 workspace per user. In this case use your existing workspace.\n", + "# client.create_workspace(WORKSPACE)\n", + "\n", + "# Create new project\n", + "client.create_project(project_name=PROJECT, namespace=WORKSPACE)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "SXimIDIDqb9J" + }, + "source": [ + "Create some users on your Mergin Maps example workspace from the provided example file in `01_users_assets/users.csv` with random permissions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WORKSPACE_ID: 15639\n" + ] + } + ], + "source": [ + "# First, let's get workspace ID\n", + "WORKSPACE_ID = None\n", + "for p in client.workspaces_list():\n", + " if p['name'] == WORKSPACE:\n", + " WORKSPACE_ID = p['id']\n", + "\n", + "print(f\"WORKSPACE_ID: {WORKSPACE_ID}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now use the client to create multiple users at once in your workspace by importing them from a CSV file. In this case, we will use the example users with random choice of password and workspace role. Username is optional, Megin Maps will generate username from email address." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Lp351dFYquVs" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "User with email jdoe@example.com created, password: PpWVMDIpGB164\n", + "User with email asmith@example.com created, password: yvhWPxvZkU230\n", + "User with email bwilliams@example.com created, password: ofgPmxerDW473\n", + "User with email cjohnson@example.com created, password: ktAhUKuOnu295\n", + "User with email omartin@example.com created, password: hIWhvYoNNh661\n" + ] + } + ], + "source": [ + "from os.path import join\n", + "import csv\n", + "import string\n", + "import random\n", + "\n", + "from mergin.common import WorkspaceRole\n", + "\n", + "filename = \"01_users_assets/users.csv\"\n", + "\n", + "with open(filename, mode=\"r\", newline=\"\", encoding=\"utf-8\") as csvfile:\n", + " reader = csv.reader(csvfile)\n", + " header = next(reader) # Skip header\n", + " for row in reader:\n", + " username = row[0]\n", + " email = row[1]\n", + " # add new mergin maps user\n", + " password = \"\".join(\n", + " random.choices(string.ascii_uppercase + string.ascii_lowercase, k=10) + random.choices(string.digits, k=3)\n", + " )\n", + " client.create_user(\n", + " email=email,\n", + " password=password,\n", + " workspace_id=WORKSPACE_ID,\n", + " workspace_role=WorkspaceRole.READER,\n", + " username=username,\n", + " )\n", + " print(f\"User with email {email} created, password: {password}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now list all users in workspace using `list_workspace_members` method." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'email': 'asmith@example.com', 'id': 22597, 'username': 'asmith', 'workspace_role': 'reader'}, {'email': 'bwilliams@example.com', 'id': 22598, 'username': 'bwilliams', 'workspace_role': 'reader'}, {'email': 'cjohnson@example.com', 'id': 22599, 'username': 'cjohnson', 'workspace_role': 'reader'}, {'email': 'jdoe@example.com', 'id': 22596, 'username': 'jdoe', 'workspace_role': 'reader'}, {'email': 'marcel.kocisek+r22overviews@lutraconsulting.co.uk', 'id': 12800, 'username': 'r22overviews', 'workspace_role': 'owner'}, {'email': 'omartin@example.com', 'id': 22600, 'username': 'omartin', 'workspace_role': 'reader'}]\n" + ] + } + ], + "source": [ + "members = client.list_workspace_members(WORKSPACE_ID)\n", + "print(members)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OoCFI9u7uClQ" + }, + "source": [ + "Let's change workspace permission level for a specific user to `EDITOR`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6QG8Smj2uI2l" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Changing role of user asmith to editor\n" + ] + }, + { + "data": { + "text/plain": [ + "{'email': 'asmith@example.com',\n", + " 'id': 22597,\n", + " 'projects_roles': [],\n", + " 'username': 'asmith',\n", + " 'workspace_role': 'editor'}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "new_editor = members[0]\n", + "print(f\"Changing role of user {new_editor['username']} to editor\")\n", + "client.update_workspace_member(\n", + " user_id=new_editor.get(\"id\", 0), workspace_id=WORKSPACE_ID, workspace_role=WorkspaceRole.EDITOR\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PpSxZfRfujj7" + }, + "source": [ + "The user is now an editor for the entire workspace and every project within it. If you want to change the user's role to 'writer' for a specific project, you need to add them as a project collaborator." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "5_7lhNhVuukI" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'email': 'asmith@example.com',\n", + " 'id': 22597,\n", + " 'project_role': 'writer',\n", + " 'role': 'writer',\n", + " 'username': 'asmith',\n", + " 'workspace_role': 'editor'}" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from mergin.common import ProjectRole\n", + "\n", + "# find project id of project used in this example\n", + "PROJECT_ID = None\n", + "for p in client.projects_list(namespace=WORKSPACE):\n", + " if p[\"name\"] == PROJECT:\n", + " PROJECT_ID = p[\"id\"]\n", + "\n", + "\n", + "client.add_project_collaborator(\n", + " user=new_editor.get(\"email\", \"\"), project_id=PROJECT_ID, project_role=ProjectRole.WRITER\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now see that the user has beed added to project with different role than the `workspace_role`. Project role is now `writer`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now upgrade user to full control over project." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'email': 'asmith@example.com',\n", + " 'id': 22597,\n", + " 'project_role': 'owner',\n", + " 'role': 'owner',\n", + " 'username': 'asmith',\n", + " 'workspace_role': 'editor'}" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.update_project_collaborator(\n", + " user_id=new_editor.get(\"id\", 0), project_id=PROJECT_ID, project_role=ProjectRole.OWNER\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To simply remove that user from project and use his default role, you can use the `remove_project_collaborator` method." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "client.remove_project_collaborator(\n", + " user_id=new_editor.get(\"id\", 0), project_id=PROJECT_ID\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check which users have access to the project using `list_project_collaborators` method." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'email': 'asmith@example.com',\n", + " 'id': 22597,\n", + " 'project_role': None,\n", + " 'role': 'editor',\n", + " 'username': 'asmith',\n", + " 'workspace_role': 'editor'},\n", + " {'email': 'bwilliams@example.com',\n", + " 'id': 22598,\n", + " 'project_role': None,\n", + " 'role': 'reader',\n", + " 'username': 'bwilliams',\n", + " 'workspace_role': 'reader'},\n", + " {'email': 'cjohnson@example.com',\n", + " 'id': 22599,\n", + " 'project_role': None,\n", + " 'role': 'reader',\n", + " 'username': 'cjohnson',\n", + " 'workspace_role': 'reader'},\n", + " {'email': 'jdoe@example.com',\n", + " 'id': 22596,\n", + " 'project_role': None,\n", + " 'role': 'reader',\n", + " 'username': 'jdoe',\n", + " 'workspace_role': 'reader'},\n", + " {'email': 'marcel.kocisek+r22overviews@lutraconsulting.co.uk',\n", + " 'id': 12800,\n", + " 'project_role': None,\n", + " 'role': 'owner',\n", + " 'username': 'r22overviews',\n", + " 'workspace_role': 'owner'},\n", + " {'email': 'omartin@example.com',\n", + " 'id': 22600,\n", + " 'project_role': None,\n", + " 'role': 'reader',\n", + " 'username': 'omartin',\n", + " 'workspace_role': 'reader'}]" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.list_project_collaborators(project_id=PROJECT_ID)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Remove a user from an workspace to completely remove them from every project." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "client.remove_workspace_member(user_id=new_editor.get(\"id\", 0), workspace_id=WORKSPACE_ID)" + ] + } + ], + "metadata": { + "colab": { + "authorship_tag": "ABX9TyPXXcFNdfLOsA7CkWfkKXfJ", + "collapsed_sections": [ + "x8IVfy9K5Z4l", + "7VMIPQo2yTmQ" + ], + "provenance": [] + }, + "kernelspec": { + "display_name": "python-api-client", + "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.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/01_users_assets/users.csv b/examples/01_users_assets/users.csv new file mode 100644 index 00000000..4a2e2f66 --- /dev/null +++ b/examples/01_users_assets/users.csv @@ -0,0 +1,6 @@ +username,email +jdoe,jdoe@example.com +asmith,asmith@example.com +bwilliams,bwilliams@example.com +cjohnson,cjohnson@example.com +omartin,omartin@example.com \ No newline at end of file diff --git a/examples/02_sync.ipynb b/examples/02_sync.ipynb new file mode 100644 index 00000000..04477623 --- /dev/null +++ b/examples/02_sync.ipynb @@ -0,0 +1,265 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "eff75b76", + "metadata": {}, + "source": [ + "# Mergin Maps Synchronisation\n", + "\n", + "Mergin Maps synchronisation operates using a push/pull mechanism for your project. \n", + "\n", + "- **Push**: Synchronise your local project changes to the Mergin Maps server\n", + "- **Pull**: Updates from the server are synchronised to the local device\n", + "\n", + "## Example project\n", + "\n", + "Imagine you are preparing a project for tree surveyors in Vienna.\n", + "\n", + "The task for the surveyors is to collect data about existing trees in the city. They are focusing on the health of the trees. In this example, we will use the Mergin Maps Python API client to automatically synchronise data to the Mergin Maps server. We will import CSV data into a GeoPackage and synchronize it. This GeoPackage can then be used for further data collection in the field." + ] + }, + { + "cell_type": "markdown", + "id": "8eb25fff", + "metadata": {}, + "source": [ + "Let's install Mergin Maps client and necessary libraries for this example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33ac4583", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install mergin-client" + ] + }, + { + "cell_type": "markdown", + "id": "611a93c1", + "metadata": {}, + "source": [ + "Fill the following variables with your Mergin Maps credentials (username / email and password)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1bd4f48d", + "metadata": {}, + "outputs": [], + "source": [ + "LOGIN=\"...\"\n", + "PASSWORD=\"...\"" + ] + }, + { + "cell_type": "markdown", + "id": "8a68900f", + "metadata": {}, + "source": [ + "Let's login to your account to be able to use the `MerginClient` class methods to automate your workflows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c332f11f", + "metadata": {}, + "outputs": [], + "source": [ + "import mergin\n", + "\n", + "client = mergin.MerginClient(\n", + " login=LOGIN,\n", + " password=PASSWORD\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "3b3b55cd", + "metadata": {}, + "source": [ + "Now you can use the client to call the API. Let's try to clone the project available for this example (`lutraconsulting/Vienna trees example`) to your Mergin Maps project. You need to specify to which project our sample project will be cloned to (edit the `PROJECT` variable in the form `{WORKSPACE NAME}/{PROJECT NAME}` in Mergin Maps cloud)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70f17d60", + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT=\".../...\"\n", + "\n", + "client.clone_project(\"lutraconsulting/Vienna trees example\", PROJECT)" + ] + }, + { + "cell_type": "markdown", + "id": "ff9dd71b", + "metadata": {}, + "source": [ + "Project contains GeoPackage `Ready to survey trees` where surveyors can collect trees health and `vienna_trees_gansehauffel.csv` file with all trees from Gänsehäufel in Vienna. Let's download project to your computer using `download_project` method. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08fc0642", + "metadata": {}, + "outputs": [], + "source": [ + "# download project to local folder.\n", + "LOCAL_FOLDER=\"/tmp/project\"\n", + "\n", + "client.download_project(PROJECT, LOCAL_FOLDER)" + ] + }, + { + "cell_type": "markdown", + "id": "400194dc", + "metadata": {}, + "source": [ + "We can now add sample points from the `.csv` file to the GeoPackage. These points within the GeoPackage will then be available to surveyors in the field for editing the health column using the Mergin Maps mobile app." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23469139", + "metadata": {}, + "outputs": [], + "source": [ + "# Install geopandas to export csv to geopackage\n", + "!pip install geopandas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f3002ce3", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import geopandas as gpd\n", + "import os\n", + "\n", + "# Get the data from the CSV (use just sample of data)\n", + "csv_file = os.path.join(LOCAL_FOLDER, \"vienna_trees_gansehauffel.csv\")\n", + "csv_df = pd.read_csv(csv_file, nrows=20, dtype={\"health\": str})\n", + "# Convert geometry in WKT format to GeoDataFrame\n", + "gdf = gpd.GeoDataFrame(csv_df, geometry=gpd.GeoSeries.from_wkt(csv_df.geometry))\n", + "print(gdf.head())\n", + "# Save the GeoDataFrame to a Geopackage\n", + "gdf.to_file(\n", + " os.path.join(LOCAL_FOLDER, \"Ready_to_survey_trees.gpkg\"), \n", + " layer=\"Ready_to_survey_trees\", driver=\"GPKG\",\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "d440ee5d", + "metadata": {}, + "source": [ + "You can now see changes in GeoPackage file using `project_status` method. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1a67839", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'path': 'Ready_to_survey_trees.gpkg', 'checksum': '3ba7658d231fefe30d9410f41c75f37d1ba5e614', 'size': 98304, 'mtime': datetime.datetime(2025, 5, 27, 16, 24, 30, 122463, tzinfo=tzlocal()), 'origin_checksum': '19b3331abc515a955691401918804d8bcf397ee4', 'chunks': ['be489067-d078-4862-bae1-c1cb222f680a']}]\n" + ] + } + ], + "source": [ + "_, push_changes, __ = client.project_status(LOCAL_FOLDER)\n", + "print(push_changes.get(\"updated\"))" + ] + }, + { + "cell_type": "markdown", + "id": "506cfa68", + "metadata": {}, + "source": [ + "You can now use `push_project` method to push data to the server." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a167b17", + "metadata": {}, + "outputs": [], + "source": [ + "client.push_project(LOCAL_FOLDER)" + ] + }, + { + "cell_type": "markdown", + "id": "caccc6da", + "metadata": {}, + "source": [ + "To pull the latest version of the project, use `pull_project` method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90e5e64a", + "metadata": {}, + "outputs": [], + "source": [ + "client.pull_project(LOCAL_FOLDER)" + ] + }, + { + "cell_type": "markdown", + "id": "e65bb7c9", + "metadata": {}, + "source": [ + "Mobile app users are now enabled to perform updates to the imported tree data directly in the field.\n", + "\n", + "\"drawing\"\n", + "\n", + "Editing tree health with predefined values.\n", + "\n", + "\"drawing\"" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python-api-client", + "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.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/02_sync_assets/edit_tree_health.jpg b/examples/02_sync_assets/edit_tree_health.jpg new file mode 100644 index 00000000..4f42e268 Binary files /dev/null and b/examples/02_sync_assets/edit_tree_health.jpg differ diff --git a/examples/02_sync_assets/synchronized_trees.jpg b/examples/02_sync_assets/synchronized_trees.jpg new file mode 100644 index 00000000..5fa82407 Binary files /dev/null and b/examples/02_sync_assets/synchronized_trees.jpg differ diff --git a/examples/03_projects.ipynb b/examples/03_projects.ipynb new file mode 100644 index 00000000..f2f45bdb --- /dev/null +++ b/examples/03_projects.ipynb @@ -0,0 +1,323 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1db9d8bc-6de0-47dc-90f3-400e395bc3e2", + "metadata": {}, + "source": [ + "# Mergin Maps Projects Management" + ] + }, + { + "cell_type": "markdown", + "id": "8c4330a7-a7ad-441d-9f2d-16d5e0dfc8f7", + "metadata": {}, + "source": [ + "Mergin Maps API allows you to manage your projects in a simple and effective way. See the [API reference](https://merginmaps.com/docs/dev/integration/) for more details about methods used in this notebook.\n", + "\n", + "First let's install Mergin Maps client (if not installed yet)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb42bae4-f313-4196-9299-2c8d2dacca11", + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "!pip install mergin-client" + ] + }, + { + "cell_type": "markdown", + "id": "f7517ef0-7b3f-47f6-8140-c3076ac02215", + "metadata": {}, + "source": [ + "Login to Mergin Maps using your an existing user" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c380887c-271b-48b6-b435-ed8628dd0a81", + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "# Use here your login username and password\n", + "LOGIN=\"...\"\n", + "PASSWORD=\"...\"\n", + "\n", + "import mergin\n", + "\n", + "client = mergin.MerginClient(login=LOGIN, password=PASSWORD)" + ] + }, + { + "cell_type": "markdown", + "id": "46bcada4-5dac-44fe-8c63-5f35932404c4", + "metadata": {}, + "source": [ + "Now you can use the client to call the API. Let's try to clone the project available for this example (`lutraconsulting/Vienna trees example`) to your Mergin Maps projects. \n", + "\n", + "You need to specify a workspace and project where our sample project will be cloned to. We can split our template to two projects." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1dfb7e3c-bf38-4867-b47e-b895c4672212", + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "WORKSPACE = \"...\"\n", + "PROJECT = \"project\"\n", + "\n", + "client.clone_project(\n", + " source_project_path=f\"lutraconsulting/Vienna trees example\", cloned_project_name=f\"{WORKSPACE}/{PROJECT}-team-a\"\n", + ")\n", + "client.clone_project(\n", + " source_project_path=f\"lutraconsulting/Vienna trees example\", cloned_project_name=f\"{WORKSPACE}/{PROJECT}-team-b\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "75b88a5e-efc3-4569-a15e-6f49c591180f", + "metadata": {}, + "source": [ + "Download your newly created Mergin Maps project to prepare data for your teams (Team A and Team B)." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "34f8279f-76b9-452b-b067-20923f0783b6", + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "# download project to local folder.\n", + "LOCAL_FOLDER_TEAM_A=\"/tmp/project-team-a\"\n", + "LOCAL_FOLDER_TEAM_B=\"/tmp/project-team-b\"\n", + "client.download_project(project_path=f\"{WORKSPACE}/{PROJECT}-team-a\", directory=LOCAL_FOLDER_TEAM_A)\n", + "client.download_project(project_path=f\"{WORKSPACE}/{PROJECT}-team-b\", directory=LOCAL_FOLDER_TEAM_B)" + ] + }, + { + "cell_type": "markdown", + "id": "10ea77c1-98b7-4d22-ac2c-a2b051db400e", + "metadata": {}, + "source": [ + "Let's add 20 trees to the GeoPackage for each team. See GeoPackages prepared for this example in the [./03_projects_assets](./03_projects_assets) folder." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11c18fc4-3202-498f-a733-7bf76f069926", + "metadata": { + "trusted": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'/tmp/project-team-b/Ready_to_survey_trees.gpkg'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# copy GeoPackage for team A to .gpkg file in LOCAL_FOLDER_TEAM_A\n", + "import shutil\n", + "shutil.copyfile(\n", + " f\"./03_projects_assets/Ready_to_survey_trees_team_A.gpkg\",\n", + " f\"{LOCAL_FOLDER_TEAM_A}/Ready_to_survey_trees.gpkg\"\n", + ")\n", + "# copy GeoPackage for team B to .gpkg file in LOCAL_FOLDER_TEAM_B\n", + "import shutil\n", + "shutil.copyfile(\n", + " f\"./03_projects_assets/Ready_to_survey_trees_team_B.gpkg\",\n", + " f\"{LOCAL_FOLDER_TEAM_B}/Ready_to_survey_trees.gpkg\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "4ab30e8b", + "metadata": {}, + "source": [ + "If you open your QGIS with projects for teams, you can see that every team is having different points to check.\n", + "\n", + "\"Team \"Team" + ] + }, + { + "cell_type": "markdown", + "id": "a53d4a66", + "metadata": {}, + "source": [ + "Let's sync your changes to Mergin Maps." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ce7aafe2-4e9d-4990-89f4-3654d9e15987", + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "client.push_project(directory=f\"{LOCAL_FOLDER_TEAM_A}\")\n", + "client.push_project(directory=f\"{LOCAL_FOLDER_TEAM_B}\")" + ] + }, + { + "cell_type": "markdown", + "id": "796d3cd2", + "metadata": {}, + "source": [ + "If you have prepared data for your teams, you can now add some surveyors to projects. We can use the guest user role for this, as it only grants access to a specific projects. Let's add `john.doeA@example.com` and `john.doeB@example.com` to the teams." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "d25078b0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WORKSPACE_ID: 60538\n" + ] + } + ], + "source": [ + "from mergin.common import WorkspaceRole\n", + "\n", + "# First, let's get workspace ID\n", + "WORKSPACE_ID = None\n", + "for p in client.workspaces_list():\n", + " if p['name'] == WORKSPACE:\n", + " WORKSPACE_ID = p['id']\n", + "\n", + "print(f\"WORKSPACE_ID: {WORKSPACE_ID}\")\n", + "\n", + "user_team_a = client.create_user(\n", + " email=\"john.doeA@example.com\",\n", + " password=\"JohnDoe123\",\n", + " workspace_id=WORKSPACE_ID,\n", + " workspace_role=WorkspaceRole.GUEST\n", + ")\n", + "user_team_b = client.create_user(\n", + " email=\"john.doeB@example.com\",\n", + " password=\"JohnDoe123\",\n", + " workspace_id=WORKSPACE_ID,\n", + " workspace_role=WorkspaceRole.GUEST\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "5ffce828", + "metadata": {}, + "source": [ + "Created users do not have any permissions yet. Let's add users as collaborators to the projects. They will be able to start the survey with Mergin Maps mobile app." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd204b84", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'email': 'john.doeB@example.com',\n", + " 'id': 122393,\n", + " 'project_role': 'editor',\n", + " 'role': 'editor',\n", + " 'username': 'john.doeb',\n", + " 'workspace_role': 'guest'}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from mergin.common import ProjectRole\n", + "\n", + "team_a_project = client.project_info(f\"{WORKSPACE}/{PROJECT}-team-a\")\n", + "team_b_project = client.project_info(f\"{WORKSPACE}/{PROJECT}-team-b\")\n", + "\n", + "# Add users to the projects\n", + "client.add_project_collaborator(\n", + " project_id=team_a_project.get(\"id\"), user=\"john.doeA@example.com\", project_role=ProjectRole.EDITOR\n", + ")\n", + "client.add_project_collaborator(\n", + " project_id=team_b_project.get(\"id\"), user=\"john.doeB@example.com\", project_role=ProjectRole.EDITOR\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "51879308-c8aa-480e-9f8c-530f282f4ec6", + "metadata": {}, + "source": [ + "Let's delete cloned projects to make cleanup after the example.\n", + "\n", + "NOTE: using `delete_project_now` will bypass the default value `DELETED_PROJECT_EXPIRATION`. See: https://merginmaps.com/docs/server/environment/#data-synchronisation-and-management" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "62abc678-75fe-4f7e-9f71-8dddee05d81e", + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "client.delete_project_now(f\"{WORKSPACE}/{PROJECT}-team-a\")\n", + "client.delete_project_now(f\"{WORKSPACE}/{PROJECT}-team-b\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "python-api-client", + "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.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/03_projects_assets/Ready_to_survey_trees_team_A.gpkg b/examples/03_projects_assets/Ready_to_survey_trees_team_A.gpkg new file mode 100644 index 00000000..9d8728b9 Binary files /dev/null and b/examples/03_projects_assets/Ready_to_survey_trees_team_A.gpkg differ diff --git a/examples/03_projects_assets/Ready_to_survey_trees_team_B.gpkg b/examples/03_projects_assets/Ready_to_survey_trees_team_B.gpkg new file mode 100644 index 00000000..27e4dd2f Binary files /dev/null and b/examples/03_projects_assets/Ready_to_survey_trees_team_B.gpkg differ diff --git a/examples/03_projects_assets/team_a.png b/examples/03_projects_assets/team_a.png new file mode 100644 index 00000000..a9325e86 Binary files /dev/null and b/examples/03_projects_assets/team_a.png differ diff --git a/examples/03_projects_assets/team_b.png b/examples/03_projects_assets/team_b.png new file mode 100644 index 00000000..712f4ae4 Binary files /dev/null and b/examples/03_projects_assets/team_b.png differ diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..b41a1979 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,14 @@ +# Jupyter Notebooks examples gallery + +Here you can find some tailored Jupyter Notebooks examples on how to interact with Mergin Maps Python API client. + +These examples are split into scenarios in order to highlight some of the core capabilities of the Mergin Maps API. + +## [Scenario 1](01_users.ipynb) - Users Management +On this scenario you'll create some random users from an existing CSV into Mergin Maps, change user's `ROLE` on a `WORKSPACE` and `PROJECT` perspective as well remove user's from a specific project. + +## [Scenario 2](02_sync.ipynb) - Synchronization +On this scenario you'll learn on how to do basic synchronisation (`PUSH`, `PULL`) of projects with the Mergin Maps Python API client. + +## [Scenario 3](03_projects.ipynb) - Projects Management +On this scenario you'll learn how to manage project with the Mergin Maps Python API client, namely how to clone projects and how to separate team members in isolated projects from the cloned template. \ No newline at end of file