toga-positron 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.1
2
+ Name: toga-positron
3
+ Version: 0.5.0
4
+ Summary: A Briefcase plugin for generating Positron apps.
5
+ License: New BSD
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: briefcase>=0.3.21
8
+
9
+ # toga-positron
10
+
11
+ A [Briefcase](https://github.com/beeware/briefcase) bootstrap for setting up
12
+ [Toga](https://github.com/beeware/toga) apps whose GUI generated by displaying web
13
+ content. (i.e., Electron-like apps... but more positive, because they're using Python!)
14
+
15
+ ## Usage
16
+
17
+ Create a fresh virtual environment, then install `toga-positron`. This will install
18
+ Briefcase, plus the Positron bootstrap. You can then use the Briefcase wizard to create
19
+ a new Toga app:
20
+
21
+ $ python -m venv venv
22
+ $ source venv/bin/activate
23
+ (venv) $ pip install toga-positron
24
+ (venv) $ briefcase new
25
+
26
+ This will ask you a number of questions about the app you want to generate, such as the
27
+ app's name, the authors name, and the project license. You'll then be asked which GUI
28
+ framework you want to use:
29
+
30
+ -- GUI framework -------------------------------------------------------------
31
+
32
+ What GUI toolkit do you want to use for this project?
33
+
34
+ Additional GUI bootstraps are available from the community.
35
+
36
+ Check them out at https://beeware.org/bee/briefcase-bootstraps
37
+
38
+ 1) Toga
39
+ 2) PySide6 (does not support iOS/Android/Web deployment)
40
+ 3) Pygame (does not support iOS/Android/Web deployment)
41
+ 4) Console (does not support iOS/Android/Web deployment)
42
+ 5) Toga Positron (Django server) (does not support Web deployment)
43
+ 6) Toga Positron (Site-specific browser) (does not support Web deployment)
44
+ 7) Toga Positron (Static server) (does not support Web deployment)
45
+ 8) None
46
+
47
+ GUI framework [1]:
48
+
49
+ This provides 3 options for a Toga Positron-based app:
50
+
51
+ ### Django server
52
+
53
+ An app that runs a Django web server in a background thread, and points the app's web
54
+ browser at the URL for that server. Some additional files (such as `urls.py` and
55
+ `settings.py`) will be generated by the app template.
56
+
57
+ The Django site that is generated is essentially identical to the default Django project
58
+ created by `startproject`, with some minor modifications to set app-specific paths and
59
+ ensure that static files will be served.
60
+
61
+ If you select this option, you will be asked for the initial path that you want to
62
+ display in the app window. The default value is `/admin/`, which will cause the Django
63
+ Admin login page (i.e., `http://127.0.0.1/admin/`) to be the initial URL loaded by the
64
+ app, but you can choose any other URL you want (including `/` to serve the root URL
65
+ of the Django site).
66
+
67
+ To run Django management commands, use::
68
+
69
+ (venv) PYTHONPATH=src python src/manage.py <command> <args...>
70
+
71
+ The Django app will run on a SQLite3 database, stored in the user's data directory (the
72
+ location of this directory is platform specific). This database file will be created if
73
+ it doesn't exist, and migrations will be run on every app start.
74
+
75
+ If you need to start the database with some initial content (e.g., an initial user
76
+ login) you can use `manage.py` to create an initial database file. If there is a
77
+ `db.sqlite3` in the `src/<app name>/resources` folder when the app starts, and the
78
+ user doesn't already have a `db.sqlite3` file in their app data folder, the initial
79
+ database file will be copied into the user's data folder as a starting point.
80
+
81
+ To create an initial database, use `manage.py` - e.g.,:
82
+
83
+ (venv) PYTHONPATH=src python src/manage.py migrate
84
+ (venv) PYTHONPATH=src python src/manage.py createsuperuser
85
+
86
+ This will create an initial `db.sqlite3` file with a superuser account. All users
87
+ of the app will have this superuser account in their database.
88
+
89
+ ### Site-specific browser
90
+
91
+ An app that behaves as a web browser that displays a single, externally served URL.
92
+
93
+ If you select this option, you will be asked for the URL that you want to display in the
94
+ app window. For example, if you nominate `https://github.com` as the site URL, you will
95
+ generate an app that, when started, loads the Github homepage.
96
+
97
+ ### Static server
98
+
99
+ An app that runs a simple HTTP server on a background thread, and points the app's web
100
+ browser at the URL for that server. This is suitable for serving simple static HTML and
101
+ CSS content. The app will serve the contents of the app's `resources` folder (i.e.,
102
+ `src/<app name>/resources`) as the root URL. A placeholder `index.html` file is generated
103
+ in the resources folder as part of the template.
104
+
105
+ ## Community
106
+
107
+ Toga Positron is part of the [BeeWare suite](https://beeware.org). You can talk to the
108
+ community through:
109
+
110
+ * [@beeware@fosstodon.org on Mastodon](https://fosstodon.org/@beeware)
111
+ * [Discord](https://beeware.org/bee/chat/)
112
+ * [The Toga Github Discussions forum](https://github.com/beeware/toga/discussions)
113
+
114
+ We foster a welcoming and respectful community as described in our
115
+ [BeeWare Community Code of Conduct](https://beeware.org/community/behavior/)
116
+
117
+ ## Contributing
118
+
119
+ If you experience problems with this backend, [log them on
120
+ GitHub](https://github.com/beeware/toga/issues). If you want to contribute code, please
121
+ [fork the code](https://github.com/beeware/toga) and [submit a pull
122
+ request](https://github.com/beeware/toga/pulls).
@@ -0,0 +1,114 @@
1
+ # toga-positron
2
+
3
+ A [Briefcase](https://github.com/beeware/briefcase) bootstrap for setting up
4
+ [Toga](https://github.com/beeware/toga) apps whose GUI generated by displaying web
5
+ content. (i.e., Electron-like apps... but more positive, because they're using Python!)
6
+
7
+ ## Usage
8
+
9
+ Create a fresh virtual environment, then install `toga-positron`. This will install
10
+ Briefcase, plus the Positron bootstrap. You can then use the Briefcase wizard to create
11
+ a new Toga app:
12
+
13
+ $ python -m venv venv
14
+ $ source venv/bin/activate
15
+ (venv) $ pip install toga-positron
16
+ (venv) $ briefcase new
17
+
18
+ This will ask you a number of questions about the app you want to generate, such as the
19
+ app's name, the authors name, and the project license. You'll then be asked which GUI
20
+ framework you want to use:
21
+
22
+ -- GUI framework -------------------------------------------------------------
23
+
24
+ What GUI toolkit do you want to use for this project?
25
+
26
+ Additional GUI bootstraps are available from the community.
27
+
28
+ Check them out at https://beeware.org/bee/briefcase-bootstraps
29
+
30
+ 1) Toga
31
+ 2) PySide6 (does not support iOS/Android/Web deployment)
32
+ 3) Pygame (does not support iOS/Android/Web deployment)
33
+ 4) Console (does not support iOS/Android/Web deployment)
34
+ 5) Toga Positron (Django server) (does not support Web deployment)
35
+ 6) Toga Positron (Site-specific browser) (does not support Web deployment)
36
+ 7) Toga Positron (Static server) (does not support Web deployment)
37
+ 8) None
38
+
39
+ GUI framework [1]:
40
+
41
+ This provides 3 options for a Toga Positron-based app:
42
+
43
+ ### Django server
44
+
45
+ An app that runs a Django web server in a background thread, and points the app's web
46
+ browser at the URL for that server. Some additional files (such as `urls.py` and
47
+ `settings.py`) will be generated by the app template.
48
+
49
+ The Django site that is generated is essentially identical to the default Django project
50
+ created by `startproject`, with some minor modifications to set app-specific paths and
51
+ ensure that static files will be served.
52
+
53
+ If you select this option, you will be asked for the initial path that you want to
54
+ display in the app window. The default value is `/admin/`, which will cause the Django
55
+ Admin login page (i.e., `http://127.0.0.1/admin/`) to be the initial URL loaded by the
56
+ app, but you can choose any other URL you want (including `/` to serve the root URL
57
+ of the Django site).
58
+
59
+ To run Django management commands, use::
60
+
61
+ (venv) PYTHONPATH=src python src/manage.py <command> <args...>
62
+
63
+ The Django app will run on a SQLite3 database, stored in the user's data directory (the
64
+ location of this directory is platform specific). This database file will be created if
65
+ it doesn't exist, and migrations will be run on every app start.
66
+
67
+ If you need to start the database with some initial content (e.g., an initial user
68
+ login) you can use `manage.py` to create an initial database file. If there is a
69
+ `db.sqlite3` in the `src/<app name>/resources` folder when the app starts, and the
70
+ user doesn't already have a `db.sqlite3` file in their app data folder, the initial
71
+ database file will be copied into the user's data folder as a starting point.
72
+
73
+ To create an initial database, use `manage.py` - e.g.,:
74
+
75
+ (venv) PYTHONPATH=src python src/manage.py migrate
76
+ (venv) PYTHONPATH=src python src/manage.py createsuperuser
77
+
78
+ This will create an initial `db.sqlite3` file with a superuser account. All users
79
+ of the app will have this superuser account in their database.
80
+
81
+ ### Site-specific browser
82
+
83
+ An app that behaves as a web browser that displays a single, externally served URL.
84
+
85
+ If you select this option, you will be asked for the URL that you want to display in the
86
+ app window. For example, if you nominate `https://github.com` as the site URL, you will
87
+ generate an app that, when started, loads the Github homepage.
88
+
89
+ ### Static server
90
+
91
+ An app that runs a simple HTTP server on a background thread, and points the app's web
92
+ browser at the URL for that server. This is suitable for serving simple static HTML and
93
+ CSS content. The app will serve the contents of the app's `resources` folder (i.e.,
94
+ `src/<app name>/resources`) as the root URL. A placeholder `index.html` file is generated
95
+ in the resources folder as part of the template.
96
+
97
+ ## Community
98
+
99
+ Toga Positron is part of the [BeeWare suite](https://beeware.org). You can talk to the
100
+ community through:
101
+
102
+ * [@beeware@fosstodon.org on Mastodon](https://fosstodon.org/@beeware)
103
+ * [Discord](https://beeware.org/bee/chat/)
104
+ * [The Toga Github Discussions forum](https://github.com/beeware/toga/discussions)
105
+
106
+ We foster a welcoming and respectful community as described in our
107
+ [BeeWare Community Code of Conduct](https://beeware.org/community/behavior/)
108
+
109
+ ## Contributing
110
+
111
+ If you experience problems with this backend, [log them on
112
+ GitHub](https://github.com/beeware/toga/issues). If you want to contribute code, please
113
+ [fork the code](https://github.com/beeware/toga) and [submit a pull
114
+ request](https://github.com/beeware/toga/pulls).
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = [
3
+ # keep versions in sync with ../pyproject.toml
4
+ "setuptools==75.3.0",
5
+ "setuptools_scm==8.1.0",
6
+ "setuptools_dynamic_dependencies @ git+https://github.com/beeware/setuptools_dynamic_dependencies",
7
+ ]
8
+ build-backend = "setuptools.build_meta"
9
+
10
+ [project]
11
+ name = "toga-positron"
12
+ description = "A Briefcase plugin for generating Positron apps."
13
+ readme = "README.md"
14
+ license.text = "New BSD"
15
+ dynamic = ["version"]
16
+ dependencies = ["briefcase >= 0.3.21"]
17
+
18
+ [project.entry-points."briefcase.bootstraps"]
19
+ "Toga Positron (Django server)" = "positron.django:DjangoPositronBootstrap"
20
+ "Toga Positron (Static server)" = "positron.static:StaticPositronBootstrap"
21
+ "Toga Positron (Site-specific browser)" = "positron.sitespecific:SiteSpecificPositronBootstrap"
22
+
23
+ [tool.setuptools_scm]
24
+ root = "../"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from briefcase.bootstraps import TogaGuiBootstrap
7
+
8
+
9
+ def validate_path(value: str) -> bool:
10
+ """Validate that the value is a valid path."""
11
+ if not value.startswith("/"):
12
+ raise ValueError("Path must start with a /")
13
+ return True
14
+
15
+
16
+ def templated_content(template_name, **context):
17
+ """Render a template for `template.name` with the provided context."""
18
+ template = (
19
+ Path(__file__).parent / f"django_templates/{template_name}.tmpl"
20
+ ).read_text(encoding="utf-8")
21
+ return template.format(**context)
22
+
23
+
24
+ def templated_file(template_name, output_path, **context):
25
+ """Render a template for `template.name` with the provided context, saving the
26
+ result in `output_path`."""
27
+ (output_path / template_name).write_text(
28
+ templated_content(template_name, **context), encoding="utf-8"
29
+ )
30
+
31
+
32
+ class DjangoPositronBootstrap(TogaGuiBootstrap):
33
+ display_name_annotation = "does not support Web deployment"
34
+
35
+ def app_source(self):
36
+ return templated_content("app.py", initial_path=self.initial_path)
37
+
38
+ def pyproject_table_briefcase_app_extra_content(self):
39
+ return """
40
+ requires = [
41
+ "django~=5.1",
42
+ ]
43
+ test_requires = [
44
+ {% if cookiecutter.test_framework == "pytest" %}
45
+ "pytest",
46
+ {% endif %}
47
+ ]
48
+ """
49
+
50
+ def extra_context(self, project_overrides: dict[str, str]) -> dict[str, Any] | None:
51
+ """Runs prior to other plugin hooks to provide additional context.
52
+
53
+ This can be used to prompt the user with additional questions or run arbitrary
54
+ logic to supplement the context provided to cookiecutter.
55
+
56
+ :param project_overrides: Any overrides provided by the user as -Q options that
57
+ haven't been consumed by the standard bootstrap wizard questions.
58
+ """
59
+ self.initial_path = self.console.text_question(
60
+ intro=(
61
+ "What path do you want to use as the initial URL for the app's "
62
+ "webview?\n"
63
+ "\n"
64
+ "The value should start with a '/', but can be any path that your "
65
+ "Django site will serve."
66
+ ),
67
+ description="Initial path",
68
+ default="/admin/",
69
+ validator=validate_path,
70
+ override_value=project_overrides.pop("initial_path", None),
71
+ )
72
+
73
+ return {}
74
+
75
+ def post_generate(self, base_path: Path):
76
+ app_path = base_path / "src" / self.context["module_name"]
77
+
78
+ # Top level files
79
+ self.console.debug("Writing manage.py")
80
+ templated_file(
81
+ "manage.py",
82
+ app_path.parent,
83
+ module_name=self.context["module_name"],
84
+ )
85
+ # App files
86
+ for template_name in ["settings.py", "urls.py", "wsgi.py"]:
87
+ self.console.debug(f"Writing {template_name}")
88
+ templated_file(
89
+ template_name,
90
+ app_path,
91
+ module_name=self.context["module_name"],
92
+ )
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import os
5
+ import shutil
6
+ import socketserver
7
+ from threading import Thread
8
+ from wsgiref.simple_server import WSGIServer
9
+
10
+ import django
11
+ from django.core import management as django_manage
12
+ from django.core.handlers.wsgi import WSGIHandler
13
+ from django.core.servers.basehttp import WSGIRequestHandler
14
+
15
+ import toga
16
+
17
+
18
+ class ThreadedWSGIServer(socketserver.ThreadingMixIn, WSGIServer):
19
+ pass
20
+
21
+
22
+ class {{{{ cookiecutter.class_name }}}}(toga.App):
23
+ def web_server(self):
24
+ print("Configuring settings...")
25
+ os.environ["DJANGO_SETTINGS_MODULE"] = "{{{{ cookiecutter.module_name }}}}.settings"
26
+ django.setup(set_prefix=False)
27
+
28
+ self.paths.data.mkdir(exist_ok=True)
29
+ user_db = self.paths.data / "db.sqlite3"
30
+ if user_db.exists():
31
+ print("User already has a database.")
32
+ else:
33
+ template_db = self.paths.app / "resources" / "db.sqlite3"
34
+ if template_db.exists():
35
+ print("Copying initial database...")
36
+ shutil.copy(template_db, user_db)
37
+ else:
38
+ print("No initial database.")
39
+
40
+ print("Applying database migrations...")
41
+ django_manage.call_command("migrate")
42
+
43
+ print("Starting server...")
44
+ # Use port 0 to let the server select an available port.
45
+ self._httpd = ThreadedWSGIServer(("127.0.0.1", 0), WSGIRequestHandler)
46
+ self._httpd.daemon_threads = True
47
+
48
+ wsgi_handler = WSGIHandler()
49
+ self._httpd.set_app(wsgi_handler)
50
+
51
+ # The server is now listening, but connections will block until
52
+ # serve_forever is run.
53
+ self.loop.call_soon_threadsafe(self.server_exists.set_result, "ready")
54
+ self._httpd.serve_forever()
55
+
56
+ def cleanup(self, app, **kwargs):
57
+ print("Shutting down...")
58
+ self._httpd.shutdown()
59
+ return True
60
+
61
+ def startup(self):
62
+ self.server_exists = asyncio.Future()
63
+
64
+ self.web_view = toga.WebView()
65
+
66
+ self.server_thread = Thread(target=self.web_server)
67
+ self.server_thread.start()
68
+
69
+ self.on_exit = self.cleanup
70
+
71
+ self.main_window = toga.MainWindow()
72
+ self.main_window.content = self.web_view
73
+
74
+ async def on_running(self):
75
+ await self.server_exists
76
+
77
+ host, port = self._httpd.socket.getsockname()
78
+ self.web_view.url = f"http://{{host}}:{{port}}{initial_path}"
79
+
80
+ self.main_window.show()
81
+
82
+
83
+ def main():
84
+ return {{{{ cookiecutter.class_name }}}}()
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python
2
+ """Django's command-line utility for administrative tasks."""
3
+ import os
4
+ import sys
5
+
6
+
7
+ def main():
8
+ """Run administrative tasks."""
9
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{module_name}.settings")
10
+ try:
11
+ from django.core.management import execute_from_command_line
12
+ except ImportError as exc:
13
+ raise ImportError(
14
+ "Couldn't import Django. Are you sure it's installed and "
15
+ "available on your PYTHONPATH environment variable? Did you "
16
+ "forget to activate a virtual environment?"
17
+ ) from exc
18
+ execute_from_command_line(sys.argv)
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
@@ -0,0 +1,120 @@
1
+ """
2
+ Django settings for a Positron project.
3
+
4
+ Generated by "django-admin startproject" using Django 5.1.5.
5
+
6
+ For more information on this file, see
7
+ https://docs.djangoproject.com/en/5.1/topics/settings/
8
+
9
+ For the full list of settings and their values, see
10
+ https://docs.djangoproject.com/en/5.1/ref/settings/
11
+ """
12
+
13
+ from pathlib import Path
14
+
15
+ from toga import App as TogaApp
16
+
17
+ BASE_PATH = Path(__file__).parent / "resources"
18
+
19
+ # A Positron app is only ever serving to itself, so a lot of the usual advice about
20
+ # Django best practices in production don't apply. The secret key doesn't need to be
21
+ # *that* secret; and running in debug mode (with staticfiles) is fine.
22
+ SECRET_KEY = "django-insecure-%vgal2@#0@feqe3jz@1d+f95c*@)2f9n^v9@#%&po5+ct7plwz"
23
+ DEBUG = True
24
+
25
+ ALLOWED_HOSTS = []
26
+
27
+ # Application definition
28
+
29
+ INSTALLED_APPS = [
30
+ "django.contrib.admin",
31
+ "django.contrib.auth",
32
+ "django.contrib.contenttypes",
33
+ "django.contrib.sessions",
34
+ "django.contrib.messages",
35
+ "django.contrib.staticfiles",
36
+ ]
37
+
38
+ MIDDLEWARE = [
39
+ "django.middleware.security.SecurityMiddleware",
40
+ "django.contrib.sessions.middleware.SessionMiddleware",
41
+ "django.middleware.common.CommonMiddleware",
42
+ "django.middleware.csrf.CsrfViewMiddleware",
43
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
44
+ "django.contrib.messages.middleware.MessageMiddleware",
45
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
46
+ ]
47
+
48
+ ROOT_URLCONF = "{module_name}.urls"
49
+
50
+ TEMPLATES = [
51
+ {{
52
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
53
+ "DIRS": [],
54
+ "APP_DIRS": True,
55
+ "OPTIONS": {{
56
+ "context_processors": [
57
+ "django.template.context_processors.debug",
58
+ "django.template.context_processors.request",
59
+ "django.contrib.auth.context_processors.auth",
60
+ "django.contrib.messages.context_processors.messages",
61
+ ],
62
+ }},
63
+ }},
64
+ ]
65
+
66
+ WSGI_APPLICATION = "{module_name}.wsgi.application"
67
+
68
+
69
+ # Database
70
+ # https://docs.djangoproject.com/en/5.1/ref/settings/#databases
71
+
72
+ DATABASES = {{
73
+ "default": {{
74
+ "ENGINE": "django.db.backends.sqlite3",
75
+ "NAME": (TogaApp.app.paths.data if TogaApp.app else BASE_PATH)
76
+ / "db.sqlite3",
77
+ }}
78
+ }}
79
+
80
+ # Password validation
81
+ # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
82
+
83
+ AUTH_PASSWORD_VALIDATORS = [
84
+ {{
85
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
86
+ }},
87
+ {{
88
+ "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
89
+ }},
90
+ {{
91
+ "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
92
+ }},
93
+ {{
94
+ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
95
+ }},
96
+ ]
97
+
98
+
99
+ # Internationalization
100
+ # https://docs.djangoproject.com/en/5.1/topics/i18n/
101
+
102
+ LANGUAGE_CODE = "en-us"
103
+
104
+ TIME_ZONE = "UTC"
105
+
106
+ USE_I18N = True
107
+
108
+ USE_TZ = True
109
+
110
+
111
+ # Static files (CSS, JavaScript, Images)
112
+ # https://docs.djangoproject.com/en/5.1/howto/static-files/
113
+
114
+ STATIC_URL = "static/"
115
+ STATIC_ROOT = BASE_PATH / "static"
116
+
117
+ # Default primary key field type
118
+ # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
119
+
120
+ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
@@ -0,0 +1,24 @@
1
+ """
2
+ URL configuration for foobar project.
3
+
4
+ The `urlpatterns` list routes URLs to views. For more information please see:
5
+ https://docs.djangoproject.com/en/5.1/topics/http/urls/
6
+ Examples:
7
+ Function views
8
+ 1. Add an import: from my_app import views
9
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
10
+ Class-based views
11
+ 1. Add an import: from other_app.views import Home
12
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
13
+ Including another URLconf
14
+ 1. Import the include() function: from django.urls import include, path
15
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
16
+ """
17
+ from django.contrib import admin
18
+ from django.contrib.staticfiles import views as staticfiles
19
+ from django.urls import path, re_path
20
+
21
+ urlpatterns = [
22
+ path("admin/", admin.site.urls),
23
+ re_path(r"^static/(?P<path>.*)$", staticfiles.serve),
24
+ ]
@@ -0,0 +1,15 @@
1
+ """
2
+ WSGI config for a Positron project.
3
+
4
+ It exposes the WSGI callable as a module-level variable named ``application``.
5
+
6
+ For more information on this file, see
7
+ https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
8
+ """
9
+ import os
10
+
11
+ from django.core.wsgi import get_wsgi_application
12
+
13
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{module_name}.settings")
14
+
15
+ application = get_wsgi_application()
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from briefcase.bootstraps import TogaGuiBootstrap
6
+ from briefcase.config import validate_url
7
+
8
+
9
+ class SiteSpecificPositronBootstrap(TogaGuiBootstrap):
10
+ display_name_annotation = "does not support Web deployment"
11
+
12
+ def app_source(self):
13
+ return f"""\
14
+ import toga
15
+
16
+
17
+ class {{{{ cookiecutter.class_name }}}}(toga.App):
18
+
19
+ def startup(self):
20
+ self.web_view = toga.WebView()
21
+ self.web_view.url = f"{self.site_url}"
22
+
23
+ self.main_window = toga.MainWindow()
24
+ self.main_window.content = self.web_view
25
+ self.main_window.show()
26
+
27
+
28
+ def main():
29
+ return {{{{ cookiecutter.class_name }}}}()
30
+ """
31
+
32
+ def extra_context(self, project_overrides: dict[str, str]) -> dict[str, Any] | None:
33
+ """Runs prior to other plugin hooks to provide additional context.
34
+
35
+ This can be used to prompt the user with additional questions or run arbitrary
36
+ logic to supplement the context provided to cookiecutter.
37
+
38
+ :param project_overrides: Any overrides provided by the user as -Q options that
39
+ haven't been consumed by the standard bootstrap wizard questions.
40
+ """
41
+ self.site_url = self.console.text_question(
42
+ intro="What website would you like to make a site-specific browser for?",
43
+ description="Site URL",
44
+ default="",
45
+ validator=validate_url,
46
+ override_value=project_overrides.pop("site_url", None),
47
+ )
48
+
49
+ return {}
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from briefcase.bootstraps import TogaGuiBootstrap
6
+
7
+
8
+ class StaticPositronBootstrap(TogaGuiBootstrap):
9
+ display_name_annotation = "does not support Web deployment"
10
+
11
+ def app_source(self):
12
+ return """\
13
+ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
14
+ from threading import Event, Thread
15
+
16
+ import toga
17
+
18
+
19
+ class HTTPHandler(SimpleHTTPRequestHandler):
20
+ def translate_path(self, path):
21
+ return str(self.server.base_path / path[1:])
22
+
23
+
24
+ class LocalHTTPServer(ThreadingHTTPServer):
25
+ def __init__(self, base_path, RequestHandlerClass=HTTPHandler):
26
+ self.base_path = base_path
27
+ # Use port 0 to let the server select an available port.
28
+ super().__init__(("127.0.0.1", 0), RequestHandlerClass)
29
+
30
+
31
+ class {{ cookiecutter.class_name }}(toga.App):
32
+ def web_server(self):
33
+ print("Starting server...")
34
+ self._httpd = LocalHTTPServer(self.paths.app / "resources")
35
+ # The server is now listening, but connections will block until
36
+ # serve_forever is run.
37
+ self.server_exists.set()
38
+ self._httpd.serve_forever()
39
+
40
+ def cleanup(self, app, **kwargs):
41
+ print("Shutting down...")
42
+ self._httpd.shutdown()
43
+ return True
44
+
45
+ def startup(self):
46
+ self.server_exists = Event()
47
+
48
+ self.web_view = toga.WebView()
49
+
50
+ self.server_thread = Thread(target=self.web_server)
51
+ self.server_thread.start()
52
+
53
+ self.on_exit = self.cleanup
54
+
55
+ self.server_exists.wait()
56
+ host, port = self._httpd.socket.getsockname()
57
+ self.web_view.url = f"http://{host}:{port}/"
58
+
59
+ self.main_window = toga.MainWindow()
60
+ self.main_window.content = self.web_view
61
+ self.main_window.show()
62
+
63
+
64
+ def main():
65
+ return {{ cookiecutter.class_name }}()
66
+ """
67
+
68
+ def post_generate(self, base_path: Path):
69
+ resource_path = base_path / "src" / self.context["module_name"] / "resources"
70
+
71
+ # Write an index.html file
72
+ (resource_path / "index.html").write_text(
73
+ f"""<html>
74
+ <head>
75
+ <title>{self.context["formal_name"]}</title>
76
+ <link rel="stylesheet" href="./positron.css" type="text/css">
77
+ </head>
78
+ <body>
79
+ <h1>Hello World</h1>
80
+ </body>
81
+ </html>
82
+ """,
83
+ encoding="UTF-8",
84
+ )
85
+
86
+ # Write a CSS file
87
+ (resource_path / "positron.css").write_text(
88
+ """
89
+ h1 {
90
+ font-family: sans-serif;
91
+ }
92
+ """,
93
+ encoding="UTF-8",
94
+ )
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.1
2
+ Name: toga-positron
3
+ Version: 0.5.0
4
+ Summary: A Briefcase plugin for generating Positron apps.
5
+ License: New BSD
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: briefcase>=0.3.21
8
+
9
+ # toga-positron
10
+
11
+ A [Briefcase](https://github.com/beeware/briefcase) bootstrap for setting up
12
+ [Toga](https://github.com/beeware/toga) apps whose GUI generated by displaying web
13
+ content. (i.e., Electron-like apps... but more positive, because they're using Python!)
14
+
15
+ ## Usage
16
+
17
+ Create a fresh virtual environment, then install `toga-positron`. This will install
18
+ Briefcase, plus the Positron bootstrap. You can then use the Briefcase wizard to create
19
+ a new Toga app:
20
+
21
+ $ python -m venv venv
22
+ $ source venv/bin/activate
23
+ (venv) $ pip install toga-positron
24
+ (venv) $ briefcase new
25
+
26
+ This will ask you a number of questions about the app you want to generate, such as the
27
+ app's name, the authors name, and the project license. You'll then be asked which GUI
28
+ framework you want to use:
29
+
30
+ -- GUI framework -------------------------------------------------------------
31
+
32
+ What GUI toolkit do you want to use for this project?
33
+
34
+ Additional GUI bootstraps are available from the community.
35
+
36
+ Check them out at https://beeware.org/bee/briefcase-bootstraps
37
+
38
+ 1) Toga
39
+ 2) PySide6 (does not support iOS/Android/Web deployment)
40
+ 3) Pygame (does not support iOS/Android/Web deployment)
41
+ 4) Console (does not support iOS/Android/Web deployment)
42
+ 5) Toga Positron (Django server) (does not support Web deployment)
43
+ 6) Toga Positron (Site-specific browser) (does not support Web deployment)
44
+ 7) Toga Positron (Static server) (does not support Web deployment)
45
+ 8) None
46
+
47
+ GUI framework [1]:
48
+
49
+ This provides 3 options for a Toga Positron-based app:
50
+
51
+ ### Django server
52
+
53
+ An app that runs a Django web server in a background thread, and points the app's web
54
+ browser at the URL for that server. Some additional files (such as `urls.py` and
55
+ `settings.py`) will be generated by the app template.
56
+
57
+ The Django site that is generated is essentially identical to the default Django project
58
+ created by `startproject`, with some minor modifications to set app-specific paths and
59
+ ensure that static files will be served.
60
+
61
+ If you select this option, you will be asked for the initial path that you want to
62
+ display in the app window. The default value is `/admin/`, which will cause the Django
63
+ Admin login page (i.e., `http://127.0.0.1/admin/`) to be the initial URL loaded by the
64
+ app, but you can choose any other URL you want (including `/` to serve the root URL
65
+ of the Django site).
66
+
67
+ To run Django management commands, use::
68
+
69
+ (venv) PYTHONPATH=src python src/manage.py <command> <args...>
70
+
71
+ The Django app will run on a SQLite3 database, stored in the user's data directory (the
72
+ location of this directory is platform specific). This database file will be created if
73
+ it doesn't exist, and migrations will be run on every app start.
74
+
75
+ If you need to start the database with some initial content (e.g., an initial user
76
+ login) you can use `manage.py` to create an initial database file. If there is a
77
+ `db.sqlite3` in the `src/<app name>/resources` folder when the app starts, and the
78
+ user doesn't already have a `db.sqlite3` file in their app data folder, the initial
79
+ database file will be copied into the user's data folder as a starting point.
80
+
81
+ To create an initial database, use `manage.py` - e.g.,:
82
+
83
+ (venv) PYTHONPATH=src python src/manage.py migrate
84
+ (venv) PYTHONPATH=src python src/manage.py createsuperuser
85
+
86
+ This will create an initial `db.sqlite3` file with a superuser account. All users
87
+ of the app will have this superuser account in their database.
88
+
89
+ ### Site-specific browser
90
+
91
+ An app that behaves as a web browser that displays a single, externally served URL.
92
+
93
+ If you select this option, you will be asked for the URL that you want to display in the
94
+ app window. For example, if you nominate `https://github.com` as the site URL, you will
95
+ generate an app that, when started, loads the Github homepage.
96
+
97
+ ### Static server
98
+
99
+ An app that runs a simple HTTP server on a background thread, and points the app's web
100
+ browser at the URL for that server. This is suitable for serving simple static HTML and
101
+ CSS content. The app will serve the contents of the app's `resources` folder (i.e.,
102
+ `src/<app name>/resources`) as the root URL. A placeholder `index.html` file is generated
103
+ in the resources folder as part of the template.
104
+
105
+ ## Community
106
+
107
+ Toga Positron is part of the [BeeWare suite](https://beeware.org). You can talk to the
108
+ community through:
109
+
110
+ * [@beeware@fosstodon.org on Mastodon](https://fosstodon.org/@beeware)
111
+ * [Discord](https://beeware.org/bee/chat/)
112
+ * [The Toga Github Discussions forum](https://github.com/beeware/toga/discussions)
113
+
114
+ We foster a welcoming and respectful community as described in our
115
+ [BeeWare Community Code of Conduct](https://beeware.org/community/behavior/)
116
+
117
+ ## Contributing
118
+
119
+ If you experience problems with this backend, [log them on
120
+ GitHub](https://github.com/beeware/toga/issues). If you want to contribute code, please
121
+ [fork the code](https://github.com/beeware/toga) and [submit a pull
122
+ request](https://github.com/beeware/toga/pulls).
@@ -0,0 +1,17 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/positron/__init__.py
4
+ src/positron/django.py
5
+ src/positron/sitespecific.py
6
+ src/positron/static.py
7
+ src/positron/django_templates/app.py.tmpl
8
+ src/positron/django_templates/manage.py.tmpl
9
+ src/positron/django_templates/settings.py.tmpl
10
+ src/positron/django_templates/urls.py.tmpl
11
+ src/positron/django_templates/wsgi.py.tmpl
12
+ src/toga_positron.egg-info/PKG-INFO
13
+ src/toga_positron.egg-info/SOURCES.txt
14
+ src/toga_positron.egg-info/dependency_links.txt
15
+ src/toga_positron.egg-info/entry_points.txt
16
+ src/toga_positron.egg-info/requires.txt
17
+ src/toga_positron.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ [briefcase.bootstraps]
2
+ Toga Positron (Django server) = positron.django:DjangoPositronBootstrap
3
+ Toga Positron (Site-specific browser) = positron.sitespecific:SiteSpecificPositronBootstrap
4
+ Toga Positron (Static server) = positron.static:StaticPositronBootstrap
@@ -0,0 +1 @@
1
+ briefcase>=0.3.21