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.
- toga_positron-0.5.0/PKG-INFO +122 -0
- toga_positron-0.5.0/README.md +114 -0
- toga_positron-0.5.0/pyproject.toml +24 -0
- toga_positron-0.5.0/setup.cfg +4 -0
- toga_positron-0.5.0/src/positron/__init__.py +0 -0
- toga_positron-0.5.0/src/positron/django.py +92 -0
- toga_positron-0.5.0/src/positron/django_templates/app.py.tmpl +84 -0
- toga_positron-0.5.0/src/positron/django_templates/manage.py.tmpl +22 -0
- toga_positron-0.5.0/src/positron/django_templates/settings.py.tmpl +120 -0
- toga_positron-0.5.0/src/positron/django_templates/urls.py.tmpl +24 -0
- toga_positron-0.5.0/src/positron/django_templates/wsgi.py.tmpl +15 -0
- toga_positron-0.5.0/src/positron/sitespecific.py +49 -0
- toga_positron-0.5.0/src/positron/static.py +94 -0
- toga_positron-0.5.0/src/toga_positron.egg-info/PKG-INFO +122 -0
- toga_positron-0.5.0/src/toga_positron.egg-info/SOURCES.txt +17 -0
- toga_positron-0.5.0/src/toga_positron.egg-info/dependency_links.txt +1 -0
- toga_positron-0.5.0/src/toga_positron.egg-info/entry_points.txt +4 -0
- toga_positron-0.5.0/src/toga_positron.egg-info/requires.txt +1 -0
- toga_positron-0.5.0/src/toga_positron.egg-info/top_level.txt +1 -0
|
@@ -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 = "../"
|
|
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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
briefcase>=0.3.21
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
positron
|