Repository: digitalfabrik/opendrift-leeway-webgui (language: python)

Issue:
Track dependencies in `pyproject.toml` instead of `Pipfile.lock`
I don't want to force my opinion onto this project (which I'm not part of), but based on my experience with other Django projects in the Digitalfabrik ecosystem ([lunes-cms](https://github.com/digitalfabrik/lunes-cms) and [integreat-cms](https://github.com/digitalfabrik/integreat-cms)), I would not recommend to use pipenv for new setups. Migrating the dependency manager in mid-project is a bit cumbersome, so I suggest to debate this in an early stage (though I'm aware that this is not the most urgent topic right now).

My main arguments are:
1. Installing/updating dependencies via pipenv takes longer than with plain pip (couple of mins compared to couple of seconds - in the long term, this really cumulates to a lot of wasted dev time... I think this is not worth the benefits pipenv is offering)
2. Debugging problems with pipenv itself did cost us quite some time when we cumulate this over the past years

### Proposed solution

- Follow this guide to specify dependencies in `pyproject.toml`: https://setuptools.pypa.io/en/stable/userguide/pyproject_config.html

- Use `pip install -e .` instead of `pipenv install` to install the dependencies
Track dependencies in `pyproject.toml` instead of `Pipfile.lock`
I don't want to force my opinion onto this project (which I'm not part of), but based on my experience with other Django projects in the Digitalfabrik ecosystem ([lunes-cms](https://github.com/digitalfabrik/lunes-cms) and [integreat-cms](https://github.com/digitalfabrik/integreat-cms)), I would not recommend to use pipenv for new setups. Migrating the dependency manager in mid-project is a bit cumbersome, so I suggest to debate this in an early stage (though I'm aware that this is not the most urgent topic right now).

My main arguments are:
1. Installing/updating dependencies via pipenv takes longer than with plain pip (couple of mins compared to couple of seconds - in the long term, this really cumulates to a lot of wasted dev time... I think this is not worth the benefits pipenv is offering)
2. Debugging problems with pipenv itself did cost us quite some time when we cumulate this over the past years

### Proposed solution

- Follow this guide to specify dependencies in `pyproject.toml`: https://setuptools.pypa.io/en/stable/userguide/pyproject_config.html

- Use `pip install -e .` instead of `pipenv install` to install the dependencies


Discussion / hints:
@timoludwig thx for looking into the code, every hint is valuable!

We have (so far) good experience in using pyproject.toml with poetry: https://python-poetry.org/docs/pyproject/ in the searchwing context. 

Or do you think this is overkill?
I don't have any experience with poetry, but at the first glance it seems more usable than pipenv.
However, it adds one wrapper which isn't really required anymore in my opinion - managing virtual environments with the inbuilt [venv](https://docs.python.org/3/library/venv.html) + pip has become simple enough we don't really need the extra tool.
The only part which is still annoying with pip is the updating process, but for lunes-cms we just use the [dependabot](https://github.com/digitalfabrik/lunes-cms/blob/develop/.github/dependabot.yml) for this task.
But obviously this is mostly personal preference, so I guess this decision should be made by those who are actively contributing to this project :wink:
@timoludwig thx for looking into the code, every hint is valuable!

We have (so far) good experience in using pyproject.toml with poetry: https://python-poetry.org/docs/pyproject/ in the searchwing context. 

Or do you think this is overkill?
I don't have any experience with poetry, but at the first glance it seems more usable than pipenv.
However, it adds one wrapper which isn't really required anymore in my opinion - managing virtual environments with the inbuilt [venv](https://docs.python.org/3/library/venv.html) + pip has become simple enough we don't really need the extra tool.
The only part which is still annoying with pip is the updating process, but for lunes-cms we just use the [dependabot](https://github.com/digitalfabrik/lunes-cms/blob/develop/.github/dependabot.yml) for this task.
But obviously this is mostly personal preference, so I guess this decision should be made by those who are actively contributing to this project :wink:

Fix patch:
diff --git a/Pipfile b/Pipfile
deleted file mode 100644
index 7374565..0000000
--- a/Pipfile
+++ /dev/null
@@ -1,15 +0,0 @@
-[[source]]
-url = "https://pypi.org/simple"
-verify_ssl = true
-name = "pypi"
-
-[packages]
-django = "*"
-celery = "*"
-redis = "*"
-dms2dec = "*"
-
-[dev-packages]
-
-[requires]
-python_version = "3.6"
diff --git a/Pipfile.lock b/Pipfile.lock
deleted file mode 100644
index ec4b6ad..0000000
--- a/Pipfile.lock
+++ /dev/null
@@ -1,224 +0,0 @@
-{
-    "_meta": {
-        "hash": {
-            "sha256": "b522590d71967146eef7b2f90e1c7bba96593983fa588c9ff3a6b21d4418995f"
-        },
-        "pipfile-spec": 6,
-        "requires": {
-            "python_version": "3.6"
-        },
-        "sources": [
-            {
-                "name": "pypi",
-                "url": "https://pypi.org/simple",
-                "verify_ssl": true
-            }
-        ]
-    },
-    "default": {
-        "amqp": {
-            "hashes": [
-                "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2",
-                "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==5.1.1"
-        },
-        "asgiref": {
-            "hashes": [
-                "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9",
-                "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==3.4.1"
-        },
-        "async-timeout": {
-            "hashes": [
-                "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
-                "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==4.0.2"
-        },
-        "billiard": {
-            "hashes": [
-                "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547",
-                "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"
-            ],
-            "version": "==3.6.4.0"
-        },
-        "cached-property": {
-            "hashes": [
-                "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130",
-                "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"
-            ],
-            "markers": "python_version < '3.8'",
-            "version": "==1.5.2"
-        },
-        "celery": {
-            "hashes": [
-                "sha256:8d9a3de9162965e97f8e8cc584c67aad83b3f7a267584fa47701ed11c3e0d4b0",
-                "sha256:9dab2170b4038f7bf10ef2861dbf486ddf1d20592290a1040f7b7a1259705d42"
-            ],
-            "index": "pypi",
-            "version": "==5.1.2"
-        },
-        "click": {
-            "hashes": [
-                "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
-                "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==7.1.2"
-        },
-        "click-didyoumean": {
-            "hashes": [
-                "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
-                "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
-            ],
-            "markers": "python_full_version >= '3.6.2' and python_full_version < '4.0.0'",
-            "version": "==0.3.0"
-        },
-        "click-plugins": {
-            "hashes": [
-                "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b",
-                "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"
-            ],
-            "version": "==1.1.1"
-        },
-        "click-repl": {
-            "hashes": [
-                "sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b",
-                "sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8"
-            ],
-            "version": "==0.2.0"
-        },
-        "django": {
-            "hashes": [
-                "sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121",
-                "sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d"
-            ],
-            "index": "pypi",
-            "version": "==3.2.16"
-        },
-        "dms2dec": {
-            "hashes": [
-                "sha256:38ada6416bb6050a187b7eb4f504704f4c0d5bee3bf309b8abb6a9481eb5f624",
-                "sha256:ae7359dddbb72c2c7a64c9fa96695c59c73807e56f0d5fc41e4e7e4545f0fe97"
-            ],
-            "index": "pypi",
-            "version": "==0.1"
-        },
-        "importlib-metadata": {
-            "hashes": [
-                "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e",
-                "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"
-            ],
-            "markers": "python_version < '3.8'",
-            "version": "==4.8.3"
-        },
-        "kombu": {
-            "hashes": [
-                "sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d",
-                "sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==5.1.0"
-        },
-        "packaging": {
-            "hashes": [
-                "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
-                "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==21.3"
-        },
-        "prompt-toolkit": {
-            "hashes": [
-                "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63",
-                "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"
-            ],
-            "markers": "python_full_version >= '3.6.2'",
-            "version": "==3.0.36"
-        },
-        "pyparsing": {
-            "hashes": [
-                "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
-                "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
-            ],
-            "markers": "python_full_version >= '3.6.8'",
-            "version": "==3.0.9"
-        },
-        "pytz": {
-            "hashes": [
-                "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427",
-                "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"
-            ],
-            "version": "==2022.6"
-        },
-        "redis": {
-            "hashes": [
-                "sha256:30c07511627a4c5c4d970e060000772f323174f75e745a26938319817ead7a12",
-                "sha256:46652271dc7525cd5a9667e5b0ca983c848c75b2b8f7425403395bb8379dcf25"
-            ],
-            "index": "pypi",
-            "version": "==4.3.5"
-        },
-        "setuptools": {
-            "hashes": [
-                "sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373",
-                "sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==59.6.0"
-        },
-        "six": {
-            "hashes": [
-                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
-                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.16.0"
-        },
-        "sqlparse": {
-            "hashes": [
-                "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34",
-                "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==0.4.3"
-        },
-        "typing-extensions": {
-            "hashes": [
-                "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42",
-                "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"
-            ],
-            "markers": "python_version < '3.8'",
-            "version": "==4.1.1"
-        },
-        "vine": {
-            "hashes": [
-                "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30",
-                "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==5.0.0"
-        },
-        "wcwidth": {
-            "hashes": [
-                "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
-                "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
-            ],
-            "version": "==0.2.5"
-        },
-        "zipp": {
-            "hashes": [
-                "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832",
-                "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==3.6.0"
-        }
-    },
-    "develop": {}
-}
diff --git a/README.md b/README.md
index 56db0d5..21e21cd 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
 # About
+
 This is a little Django web application that does an [OpenDrift](https://github.com/OpenDrift/opendrift) [Leeway](https://opendrift.github.io/choosing_a_model.html) simulation with a set of provided parameters via a web formular. The result is sent to a user via e-mail.
 
 This is an experimental tool to help Search and Rescue operations. An experimental service is available at [leeway.tuerantuer.org](https://leeway.tuerantuer.org).
@@ -7,44 +8,76 @@ Sample output from the leeway tool (100 points, 1km radius, south of Lampedusa):
 
 ![Example leeway output](./.github/leeway-simulation-output.png)
 
-# Install
-1. Clone this repository and `cd` into the new directory
-1. Create an virtual environment and activate it:
+
+# Installation
+
+1. Clone this repository and change into the new directory:
+   ```bash
+   gh repo clone digitalfabrik/leeway
+   cd leeway
+   ```
+2. Create an virtual environment and activate it:
    ```bash
    python3 -m venv .venv
    source .venv/bin/activate
    ```
-1. Install the dependencies:
+3. Install the dependencies:
    ```bash
-   pipenv install
+   pip install -e .
    ```
-1. Initialize the database:
+4. Initialize the database:
    ```bash
-   cd django_leeway
+   cd opendrift_leeway
    python3 manage.py migrate
    python3 manage.py createsuperuser
    ```
 
-# Development Mode
-1. Switch into the cloned project and then into the `django_leeway` subdirectory.
-1. Open two terminals.
-1. In the first terminal run:
+# Development Server
+
+1. Switch into the cloned project and then into the `opendrift_leeway` subdirectory.
+2. Open two terminals:
+   1. In the first terminal run:
+      ```bash
+      source ../.venv/bin/activate
+      python3 manage.py runserver
+      ```
+   2. In the second terminal run:
+      ```bash
+      source ../.venv/bin/activate
+      celery -A leeway worker -l INFO
+      ```
+
+
+# Packaging
+
+1. Install build dependencies: `python3 -m pip install --upgrade build twine`
+2. Build python package: `python3 -m build`
+3. Upload target distribution to PyPI: `twine upload ./dist/opendrift-leeway-*.tar.gz`
+
+
+# Production Server
+
+This details the installation on Debian with Apache2 and mod_wsgi.
+
+1. Create target directory on the production system:
    ```bash
-   source ../.venv/bin/activate
-   python3 manage.py runserver
+   sudo mkdir /opt/iopendrift-leeway
+   sudo chown www-data:www-data /opt/opendrift-leeway
    ```
-1. In the second terminal run:
+2. Create the virtual environment:
    ```bash
-   source ../.venv/bin/activate
-   celery -A leeway worker -l INFO
+   sudo -u www-data bash
+   python3 -m venv .venv
+   source .venv/bin/activate
    ```
-
-# Production Mode
-This details the installation on Debian with Apache2 and mod_wsgi.
-
-1. Clone to `/opt/leeway`
-1. Create the virtual environment as detailed in the Install chapter.
-1. Use `python3 setup.py develop` to install the application into the virtual environment.
-1. Install Docker and add the `docker` group to the `www-data` user.
-1. Configure Apache2 according to example.
-1. Set up Celery worker with `leeway-celery.service` and start the service.
+3. Install the application into the virtual environment:
+   ```bash
+   pip install opendrift-leeway
+   ```
+4. Install Docker and add the `docker` group to the `www-data` user.
+5. Create symlink to facilitate the Apache configuration:
+   ```bash
+   ln -s $(python -c "from opendrift_leeway.core import wsgi; print(wsgi.__file__)") .
+   ```
+6. Configure Apache2 according to example.
+7. Set up Celery worker with `leeway-celery.service` and start the service.
diff --git a/example-config/apache2.conf b/example-config/apache2.conf
index 41e7067..1baa8cc 100644
--- a/example-config/apache2.conf
+++ b/example-config/apache2.conf
@@ -14,18 +14,18 @@
 	SSLSessionTickets   off
 	Header always set Strict-Transport-Security "max-age=31536000"
 
-  <Directory /opt/leeway/django_leeway>
+  <Directory /opt/opendrift-leeway/>
     <Files wsgi.py>
       Require all granted
     </Files>
   </Directory>
 
-  WSGIDaemonProcess leeway.example.com processes=1 threads=1  python-home=/opt/leeway/.venv locale=en_US.UTF-8
+  WSGIDaemonProcess leeway.example.com processes=1 threads=1  python-home=/opt/opendrift-leeway/.venv locale=en_US.UTF-8
   WSGIProcessGroup leeway.example.com
-  WSGIScriptAlias / /opt/leeway/django_leeway/core/wsgi.py
+  WSGIScriptAlias / /opt/opendrift-leeway/wsgi.py
 
-  Alias /static/ /var/www/leeway/static/
-  Alias /media/ /var/www/leeway/media/
+  Alias /static/ /var/www/opendrift-leeway/static/
+  Alias /media/ /var/www/opendrift-leeway/media/
   Alias /robots.txt /srv/robots.txt
 
   <Directory /srv>
diff --git a/django_leeway/core/__init__.py b/opendrift_leeway/core/__init__.py
similarity index 100%
rename from django_leeway/core/__init__.py
rename to opendrift_leeway/core/__init__.py
diff --git a/django_leeway/core/asgi.py b/opendrift_leeway/core/asgi.py
similarity index 100%
rename from django_leeway/core/asgi.py
rename to opendrift_leeway/core/asgi.py
diff --git a/django_leeway/core/settings.py b/opendrift_leeway/core/settings.py
similarity index 100%
rename from django_leeway/core/settings.py
rename to opendrift_leeway/core/settings.py
diff --git a/django_leeway/core/urls.py b/opendrift_leeway/core/urls.py
similarity index 100%
rename from django_leeway/core/urls.py
rename to opendrift_leeway/core/urls.py
diff --git a/django_leeway/core/wsgi.py b/opendrift_leeway/core/wsgi.py
similarity index 100%
rename from django_leeway/core/wsgi.py
rename to opendrift_leeway/core/wsgi.py
diff --git a/django_leeway/leeway/__init__.py b/opendrift_leeway/leeway/__init__.py
similarity index 100%
rename from django_leeway/leeway/__init__.py
rename to opendrift_leeway/leeway/__init__.py
diff --git a/django_leeway/leeway/admin.py b/opendrift_leeway/leeway/admin.py
similarity index 100%
rename from django_leeway/leeway/admin.py
rename to opendrift_leeway/leeway/admin.py
diff --git a/django_leeway/leeway/apps.py b/opendrift_leeway/leeway/apps.py
similarity index 100%
rename from django_leeway/leeway/apps.py
rename to opendrift_leeway/leeway/apps.py
diff --git a/django_leeway/leeway/celery.py b/opendrift_leeway/leeway/celery.py
similarity index 100%
rename from django_leeway/leeway/celery.py
rename to opendrift_leeway/leeway/celery.py
diff --git a/django_leeway/leeway/forms.py b/opendrift_leeway/leeway/forms.py
similarity index 100%
rename from django_leeway/leeway/forms.py
rename to opendrift_leeway/leeway/forms.py
diff --git a/django_leeway/leeway/migrations/0001_initial.py b/opendrift_leeway/leeway/migrations/0001_initial.py
similarity index 100%
rename from django_leeway/leeway/migrations/0001_initial.py
rename to opendrift_leeway/leeway/migrations/0001_initial.py
diff --git a/django_leeway/leeway/migrations/0002_auto_20221205_1342.py b/opendrift_leeway/leeway/migrations/0002_auto_20221205_1342.py
similarity index 100%
rename from django_leeway/leeway/migrations/0002_auto_20221205_1342.py
rename to opendrift_leeway/leeway/migrations/0002_auto_20221205_1342.py
diff --git a/django_leeway/leeway/migrations/0003_leewaysimulation_user.py b/opendrift_leeway/leeway/migrations/0003_leewaysimulation_user.py
similarity index 100%
rename from django_leeway/leeway/migrations/0003_leewaysimulation_user.py
rename to opendrift_leeway/leeway/migrations/0003_leewaysimulation_user.py
diff --git a/django_leeway/leeway/migrations/0004_leewaysimulation_simulation_finished.py b/opendrift_leeway/leeway/migrations/0004_leewaysimulation_simulation_finished.py
similarity index 100%
rename from django_leeway/leeway/migrations/0004_leewaysimulation_simulation_finished.py
rename to opendrift_leeway/leeway/migrations/0004_leewaysimulation_simulation_finished.py
diff --git a/django_leeway/leeway/migrations/0005_leewaysimulation_uuid.py b/opendrift_leeway/leeway/migrations/0005_leewaysimulation_uuid.py
similarity index 100%
rename from django_leeway/leeway/migrations/0005_leewaysimulation_uuid.py
rename to opendrift_leeway/leeway/migrations/0005_leewaysimulation_uuid.py
diff --git a/django_leeway/leeway/migrations/0006_leewaysimulation_radius.py b/opendrift_leeway/leeway/migrations/0006_leewaysimulation_radius.py
similarity index 100%
rename from django_leeway/leeway/migrations/0006_leewaysimulation_radius.py
rename to opendrift_leeway/leeway/migrations/0006_leewaysimulation_radius.py
diff --git a/django_leeway/leeway/migrations/__init__.py b/opendrift_leeway/leeway/migrations/__init__.py
similarity index 100%
rename from django_leeway/leeway/migrations/__init__.py
rename to opendrift_leeway/leeway/migrations/__init__.py
diff --git a/django_leeway/leeway/models.py b/opendrift_leeway/leeway/models.py
similarity index 100%
rename from django_leeway/leeway/models.py
rename to opendrift_leeway/leeway/models.py
diff --git a/django_leeway/leeway/tasks.py b/opendrift_leeway/leeway/tasks.py
similarity index 100%
rename from django_leeway/leeway/tasks.py
rename to opendrift_leeway/leeway/tasks.py
diff --git a/django_leeway/leeway/templates/base.html b/opendrift_leeway/leeway/templates/base.html
similarity index 100%
rename from django_leeway/leeway/templates/base.html
rename to opendrift_leeway/leeway/templates/base.html
diff --git a/django_leeway/leeway/templates/leeway-simulation.html b/opendrift_leeway/leeway/templates/leeway-simulation.html
similarity index 100%
rename from django_leeway/leeway/templates/leeway-simulation.html
rename to opendrift_leeway/leeway/templates/leeway-simulation.html
diff --git a/django_leeway/leeway/templates/registration/login.html b/opendrift_leeway/leeway/templates/registration/login.html
similarity index 100%
rename from django_leeway/leeway/templates/registration/login.html
rename to opendrift_leeway/leeway/templates/registration/login.html
diff --git a/django_leeway/leeway/urls.py b/opendrift_leeway/leeway/urls.py
similarity index 100%
rename from django_leeway/leeway/urls.py
rename to opendrift_leeway/leeway/urls.py
diff --git a/django_leeway/leeway/utils.py b/opendrift_leeway/leeway/utils.py
similarity index 100%
rename from django_leeway/leeway/utils.py
rename to opendrift_leeway/leeway/utils.py
diff --git a/django_leeway/leeway/views.py b/opendrift_leeway/leeway/views.py
similarity index 100%
rename from django_leeway/leeway/views.py
rename to opendrift_leeway/leeway/views.py
diff --git a/django_leeway/manage.py b/opendrift_leeway/manage.py
similarity index 100%
rename from django_leeway/manage.py
rename to opendrift_leeway/manage.py
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..7706f4c
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,28 @@
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "opendrift-leeway"
+description = "Web GUI for Leeway simulations with OpenDrift"
+version = "2022.12.0"
+readme = "README.md"
+requires-python = ">=3.7"
+keywords = ["OpenDrift", "Leeway", "SAR", "Search", "Rescue", "Simulation", "Drift"]
+license = {text = "Apache2 2.0 License"}
+classifiers = [
+    "Development Status :: 3 - Alpha",
+    "Framework :: Django",
+    "Framework :: Django :: 4",
+    "License :: OSI Approved :: Apache Software License",
+    "Programming Language :: Python :: 3",
+]
+dependencies = [
+    "celery",
+    "django>=4.1",
+    "dms2dec",
+    "redis",
+]
+
+[tool.setuptools.packages.find]
+include = ["opendrift_leeway*"]
diff --git a/setup.py b/setup.py
index 20f7565..7dc7287 100644
--- a/setup.py
+++ b/setup.py
@@ -1,31 +1,5 @@
 #!/usr/bin/env python3
 """ Setup.py """
+from setuptools import setup
 
-import os
-import sys
-
-from setuptools import find_packages, setup
-
-sys.path.append(os.path.abspath("django_leeway"))
-# pylint: disable=wrong-import-position
-
-setup(
-    name="leeway",
-    version="1.0.0",
-    packages=find_packages("django_leeway"),
-    package_dir={"": "django_leeway"},
-    include_package_data=True,
-    scripts=["django_leeway/manage.py"],
-    author="Tür an Tür Digitalfabrik gGmbH",
-    author_email="info@integreat-app.de",
-    description="Leeway simulation",
-    license="Apache2.0",
-    keywords="SAR Search Rescue Leeway Simulation OceanDrift",
-    url="http://github.com/Digitalfabrik/leeway",
-    classifiers=[
-        "Development Status :: 5 - Production/Stable",
-        "Intended Audience :: Developers",
-        "Programming Language :: Python :: 3.9",
-    ],
-)
-
+setup()


Test patch:
diff --git a/django_leeway/leeway/tests.py b/opendrift_leeway/leeway/tests.py
similarity index 100%
rename from django_leeway/leeway/tests.py
rename to opendrift_leeway/leeway/tests.py