├── README.md ├── forge-core ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgecore │ ├── __init__.py │ ├── cli.py │ ├── core.py │ ├── default_files │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── manage.py │ │ └── wsgi.py │ └── packages.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ ├── run │ └── test └── tests │ ├── test_core.py │ └── test_packages.py ├── forge-db ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgedb │ ├── __init__.py │ ├── checks.py │ ├── cli.py │ └── container.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ └── test_core.py ├── forge-format ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgeformat │ ├── __init__.py │ └── cli.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ └── test_core.py ├── forge-googleanalytics ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── README.md ├── forgegoogleanalytics │ ├── events.py │ ├── models.py │ ├── settings.py │ ├── templates │ │ └── googleanalytics │ │ │ └── js.html │ ├── templatetags │ │ ├── __init__.py │ │ └── googleanalytics.py │ └── utils.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ ├── templates │ └── index.html │ ├── test_core.py │ └── urls.py ├── forge-heroku ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgeheroku │ ├── __init__.py │ ├── cli.py │ └── utils.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ └── test_core.py ├── forge-htmx ├── .github │ └── workflows │ │ ├── deps.yml │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── deps.yml ├── forgehtmx │ ├── __init__.py │ ├── static │ │ └── htmx │ │ │ ├── forgehtmx.js │ │ │ └── htmx.min.js │ ├── templates │ │ └── htmx │ │ │ └── js.html │ ├── templatetags │ │ ├── __init__.py │ │ └── htmx.py │ └── views.py ├── package-lock.json ├── package.json ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ └── test_views.py ├── forge-impersonate ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── README.md ├── forgeimpersonate │ ├── __init__.py │ ├── middleware.py │ ├── models.py │ ├── permissions.py │ ├── settings.py │ ├── templates │ │ ├── admin │ │ │ ├── auth │ │ │ │ └── user │ │ │ │ │ └── change_form.html │ │ │ └── users │ │ │ │ └── user │ │ │ │ └── change_form.html │ │ └── impersonate │ │ │ └── change_form_object_tools.html │ ├── templatetags │ │ ├── __init__.py │ │ └── impersonate.py │ ├── urls.py │ └── views.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ ├── templates │ └── index.html │ ├── test_core.py │ └── urls.py ├── forge-importmap ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgeimportmap │ ├── __init__.py │ ├── cli.py │ ├── core.py │ ├── generator.py │ ├── templates │ │ └── importmap │ │ │ └── scripts.html │ └── templatetags │ │ ├── __init__.py │ │ └── importmap.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── test_project │ ├── app │ ├── apps.py │ ├── models.py │ ├── static │ │ └── app.js │ ├── templates │ │ └── index.html │ └── urls.py │ ├── importmap.lock │ ├── importmap.toml │ ├── manage.py │ ├── settings.py │ ├── tests.py │ └── wsgi.py ├── forge-logging ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgelogging │ ├── __init__.py │ ├── core.py │ └── filters.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ └── test_core.py ├── forge-oauth ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgeoauth │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── exceptions.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_oauthconnection_options_and_more.py │ │ ├── 0003_alter_oauthconnection_access_token_and_more.py │ │ └── __init__.py │ ├── models.py │ ├── providers.py │ ├── templates │ │ └── oauth │ │ │ └── error.html │ ├── urls.py │ └── views.py ├── poetry.lock ├── provider_examples │ ├── __init__.py │ ├── bitbucket.py │ ├── github.py │ └── gitlab.py ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── makemigrations │ ├── mypy │ ├── pre-commit │ ├── runserver │ ├── shell │ └── test └── tests │ ├── __init__.py │ ├── manage.py │ ├── provider_tests │ ├── __init__.py │ └── test_github.py │ ├── providers │ ├── __init__.py │ ├── bitbucket.py │ ├── github.py │ └── gitlab.py │ ├── settings.py │ ├── templates │ ├── base.html │ ├── index.html │ └── login.html │ ├── test_backends.py │ ├── test_checks.py │ ├── test_models.py │ ├── test_providers.py │ ├── urls.py │ └── users │ ├── __init__.py │ ├── apps.py │ ├── migrations │ ├── 0001_initial.py │ └── __init__.py │ └── models.py ├── forge-precommit ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgeprecommit │ ├── __init__.py │ ├── cli.py │ └── install.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ └── test_core.py ├── forge-pro ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgepro │ ├── __init__.py │ └── default_settings.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── __init__.py │ ├── settings.py │ ├── templates │ └── base.html │ ├── test_core.py │ └── urls.py ├── forge-querystats ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── README.md ├── forgequerystats │ ├── __init__.py │ ├── core.py │ ├── middleware.py │ ├── sql.py │ └── templates │ │ └── querystats │ │ ├── button.html │ │ ├── querystats.html │ │ └── stafftoolbar.html ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ ├── templates │ └── index.html │ ├── test_querystats.py │ ├── test_sql.py │ └── urls.py ├── forge-requestlog ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── README.md ├── forgerequestlog │ ├── __init__.py │ ├── core.py │ ├── middleware.py │ ├── settings.py │ ├── templates │ │ └── requestlog │ │ │ ├── requestlog.html │ │ │ └── stafftoolbar.html │ └── views.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ ├── templates │ └── index.html │ ├── test_core.py │ └── urls.py ├── forge-sentry ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── README.md ├── forgesentry │ ├── __init__.py │ ├── apps.py │ ├── middleware.py │ ├── settings.py │ ├── templates │ │ └── sentry │ │ │ └── js.html │ └── templatetags │ │ ├── __init__.py │ │ └── sentry.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ ├── templates │ └── index.html │ ├── test_sentry.py │ └── urls.py ├── forge-stafftoolbar ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── README.md ├── forgestafftoolbar │ ├── __init__.py │ ├── core.py │ ├── links.py │ ├── settings.py │ ├── templates │ │ └── stafftoolbar │ │ │ └── stafftoolbar.html │ └── templatetags │ │ ├── __init__.py │ │ └── stafftoolbar.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ ├── templates │ └── index.html │ ├── test_templates.py │ └── urls.py ├── forge-stripe ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── README.md ├── forgestripe │ ├── __init__.py │ ├── apps.py │ ├── models.py │ ├── settings.py │ ├── templatetags │ │ ├── __init__.py │ │ └── stripe.py │ └── views.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ ├── templates │ └── index.html │ ├── test_core.py │ └── urls.py ├── forge-tailwind ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgetailwind │ ├── __init__.py │ ├── cli.py │ └── core.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ └── test_core.py ├── forge-test ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgetest │ ├── __init__.py │ └── cli.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ └── test_core.py ├── forge-work ├── .github │ └── workflows │ │ ├── nextrelease.yml │ │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── forgework │ ├── __init__.py │ ├── cli.py │ └── debug.py ├── poetry.lock ├── pyproject.toml ├── scripts │ ├── format │ ├── install │ ├── pre-commit │ └── test └── tests │ ├── manage.py │ ├── settings.py │ └── test_core.py ├── pro-billing ├── .github │ └── workflows │ │ ├── deps.yml │ │ └── test.yml ├── .gitignore ├── README.md ├── app │ ├── auth.py │ ├── github.py │ ├── hosts.py │ ├── models.py │ ├── packages │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── templates │ │ │ └── packages │ │ │ │ ├── pypi_detail.html │ │ │ │ └── pypi_list.html │ │ ├── urls.py │ │ └── views.py │ ├── projects │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_project_stripe_id.py │ │ │ ├── 0003_project_github_usernames.py │ │ │ ├── 0004_alter_project_github_usernames.py │ │ │ ├── 0005_project_status.py │ │ │ ├── 0006_project_pro_private_key_project_pro_public_key.py │ │ │ ├── 0007_project_packages_token.py │ │ │ ├── 0008_alter_project_name_alter_project_packages_token.py │ │ │ ├── 0009_slugify.py │ │ │ ├── 0010_alter_project_name.py │ │ │ ├── 0011_remove_project_pro_private_key_and_more.py │ │ │ ├── 0012_alter_project_options.py │ │ │ ├── 0013_alter_project_options.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── templates │ │ │ └── projects │ │ │ │ ├── license_terms.html │ │ │ │ ├── project_form.html │ │ │ │ ├── project_keys.html │ │ │ │ ├── project_list.html │ │ │ │ ├── project_terms.html │ │ │ │ └── project_token.html │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ ├── pypi_urls.py │ ├── settings.py │ ├── static │ │ ├── forge-logo.png │ │ └── src │ │ │ └── tailwind.css │ ├── teams │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_team_stripe_id.py │ │ │ └── __init__.py │ │ └── models.py │ ├── templatemail.py │ ├── templates │ │ ├── 400.html │ │ ├── 403.html │ │ ├── 404.html │ │ ├── 500.html │ │ ├── base.html │ │ ├── django │ │ │ └── forms │ │ │ │ └── default.html │ │ └── registration │ │ │ ├── login.html │ │ │ ├── password_change_done.html │ │ │ ├── password_change_form.html │ │ │ ├── password_reset_complete.html │ │ │ ├── password_reset_confirm.html │ │ │ ├── password_reset_done.html │ │ │ ├── password_reset_form.html │ │ │ └── signup.html │ ├── tests │ │ └── test_urls.py │ ├── urls.py │ ├── users │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_user_uuid_alter_user_email.py │ │ │ ├── 0003_user_packages_token.py │ │ │ └── __init__.py │ │ ├── models.py │ │ └── views.py │ └── views.py ├── poetry.lock ├── pyproject.toml ├── scripts │ └── install └── tailwind.config.js ├── quickstart.py ├── scripts ├── import ├── package-test ├── publish └── quickstart-test └── starter-template ├── .env.example ├── .github └── workflows │ └── test.yml ├── .gitignore ├── app ├── auth.py ├── forms.py ├── models.py ├── settings.py ├── static │ └── src │ │ └── tailwind.css ├── teams │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_initial.py │ │ └── __init__.py │ ├── models.py │ └── urls.py ├── templates │ ├── 400.html │ ├── 403.html │ ├── 404.html │ ├── 500.html │ ├── base.html │ ├── django │ │ └── forms │ │ │ ├── default.html │ │ │ └── errors │ │ │ └── list │ │ │ └── default.html │ ├── home.html │ └── registration │ │ ├── login.html │ │ ├── password_change_done.html │ │ ├── password_change_form.html │ │ ├── password_reset_complete.html │ │ ├── password_reset_confirm.html │ │ ├── password_reset_done.html │ │ ├── password_reset_form.html │ │ └── signup.html ├── tests │ └── test_urls.py ├── urls.py ├── users │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── users │ │ │ └── user_form.html │ └── views.py └── views.py ├── poetry.lock ├── pyproject.toml ├── scripts └── install └── tailwind.config.js /forge-core/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-core/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-core/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Publishing 6 | /dist 7 | 8 | # Python 9 | /.venv 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # OS files 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /forge-core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Dropseed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /forge-core/README.md: -------------------------------------------------------------------------------- 1 | # forge-core 2 | 3 | All Forge packages should depend on `forge-core`. 4 | 5 | It provides the following: 6 | 7 | - the `forge` CLI (autodiscovers `forge-x` commands) 8 | - default Django `manage.py`, `wsgi.py`, and `asgi.py` files 9 | - the `Forge` class with path, tmp, and executable utils 10 | -------------------------------------------------------------------------------- /forge-core/forgecore/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import Forge 2 | 3 | __all__ = ["Forge"] 4 | -------------------------------------------------------------------------------- /forge-core/forgecore/default_files/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-core/forgecore/default_files/__init__.py -------------------------------------------------------------------------------- /forge-core/forgecore/default_files/asgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.asgi import get_asgi_application 4 | 5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 6 | 7 | application = get_asgi_application() 8 | -------------------------------------------------------------------------------- /forge-core/forgecore/default_files/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-core/forgecore/default_files/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.wsgi import get_wsgi_application 4 | 5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 6 | 7 | application = get_wsgi_application() 8 | -------------------------------------------------------------------------------- /forge-core/forgecore/packages.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | 4 | def forgepackage_installed(name: str) -> bool: 5 | try: 6 | importlib.import_module(f"forge{name}") 7 | return True 8 | except ImportError as e: 9 | return False 10 | -------------------------------------------------------------------------------- /forge-core/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | 3 | name = "forge-core" 4 | packages = [ 5 | { include = "forgecore" }, 6 | ] 7 | 8 | version = "1.1.0" 9 | description = "Core library for Forge" 10 | authors = ["Dave Gaeddert "] 11 | license = "MIT" 12 | readme = "README.md" 13 | homepage = "https://www.forgepackages.com/" 14 | documentation = "https://www.forgepackages.com/docs/" 15 | repository = "https://github.com/forgepackages/forge-core" 16 | keywords = ["django", "saas", "forge", "framework"] 17 | classifiers = [ 18 | "Environment :: Web Environment", 19 | "Framework :: Django", 20 | "Framework :: Django :: 4", 21 | "Intended Audience :: Developers", 22 | "Operating System :: OS Independent", 23 | ] 24 | 25 | [tool.poetry.scripts] 26 | forge = "forgecore.cli:cli" 27 | 28 | [tool.poetry.dependencies] 29 | python = "^3.8" 30 | click = "*" 31 | 32 | [tool.poetry.dev-dependencies] 33 | black = "^22.3.0" 34 | isort = "^5.10.1" 35 | pytest = "^7.1.2" 36 | ipdb = "^0.13.9" 37 | 38 | [build-system] 39 | requires = ["poetry-core>=1.0.0"] 40 | build-backend = "poetry.core.masonry.api" 41 | -------------------------------------------------------------------------------- /forge-core/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgecore "$@" 3 | poetry run isort forgecore --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-core/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-core/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-core/scripts/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run forge "$@" 3 | -------------------------------------------------------------------------------- /forge-core/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run pytest tests 3 | -------------------------------------------------------------------------------- /forge-core/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from forgecore import Forge 2 | 3 | 4 | def test_import(): 5 | # Just make sure it loads for now 6 | forge = Forge() 7 | -------------------------------------------------------------------------------- /forge-core/tests/test_packages.py: -------------------------------------------------------------------------------- 1 | from forgecore.packages import forgepackage_installed 2 | 3 | 4 | def test_package_installed(): 5 | assert forgepackage_installed("core") 6 | 7 | 8 | def test_package_not_installed(): 9 | assert not forgepackage_installed("foo") 10 | -------------------------------------------------------------------------------- /forge-db/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-db/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-db/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Publishing 6 | /dist 7 | 8 | # Python 9 | /.venv 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # OS files 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /forge-db/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Dropseed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /forge-db/forgedb/__init__.py: -------------------------------------------------------------------------------- 1 | from .cli import cli 2 | 3 | __all__ = ["cli"] 4 | -------------------------------------------------------------------------------- /forge-db/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgedb "$@" 3 | poetry run isort forgedb --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-db/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-db/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-db/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run pytest tests 3 | poetry run forge db --help 4 | -------------------------------------------------------------------------------- /forge-db/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-db/tests/settings.py: -------------------------------------------------------------------------------- 1 | INSTALLED_APPS = [ 2 | "forgedb", 3 | ] 4 | -------------------------------------------------------------------------------- /forge-db/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from forgecore import Forge 2 | 3 | 4 | def test_import(): 5 | # Just make sure it loads for now 6 | forge = Forge() 7 | -------------------------------------------------------------------------------- /forge-format/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-format/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-format/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Publishing 6 | /dist 7 | 8 | # Python 9 | /.venv 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # OS files 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /forge-format/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Dropseed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /forge-format/README.md: -------------------------------------------------------------------------------- 1 | # forge-format 2 | 3 | A unified, opinionated code formatting command for Django projects. 4 | 5 | Uses [black](https://github.com/psf/black) and [ruff](https://github.com/charliermarsh/ruff/) to format Python code. 6 | 7 | 8 | ## Installation 9 | 10 | First, install `forge-format` from [PyPI](https://pypi.org/project/forge-format/): 11 | 12 | ```sh 13 | pip install forge-format 14 | ``` 15 | 16 | Now you will have access to the `format` command: 17 | 18 | ```sh 19 | forge format 20 | ``` 21 | 22 | Note that if you're using black + ruff for the first time, 23 | a common issue is to get a bunch of `E501 Line too long` errors on code comments. 24 | This is because black doesn't fix line lengths on comments! 25 | If there are more than you want to fix, just add this to your `pyproject.toml`: 26 | 27 | ```toml 28 | [tool.ruff] 29 | # Never enforce `E501` (line length violations). 30 | ignore = ["E501"] 31 | ``` 32 | -------------------------------------------------------------------------------- /forge-format/forgeformat/__init__.py: -------------------------------------------------------------------------------- 1 | from .cli import cli 2 | 3 | __all__ = ["cli"] 4 | -------------------------------------------------------------------------------- /forge-format/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgeformat "$@" 3 | poetry run black tests "$@" 4 | -------------------------------------------------------------------------------- /forge-format/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-format/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-format/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run pytest tests 3 | poetry run forge format --help 4 | poetry run forge-format --help 5 | -------------------------------------------------------------------------------- /forge-format/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-format/tests/settings.py: -------------------------------------------------------------------------------- 1 | INSTALLED_APPS = [ 2 | "forgeformat", 3 | ] 4 | -------------------------------------------------------------------------------- /forge-format/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from forgecore import Forge 2 | 3 | 4 | def test_import(): 5 | # Just make sure it loads for now 6 | Forge() 7 | -------------------------------------------------------------------------------- /forge-googleanalytics/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | with: 16 | prepare_cmd: | 17 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 18 | publish_cmd: | 19 | pip3 install -U pip poetry 20 | poetry build 21 | gh release upload $TAG dist/* 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-googleanalytics/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-googleanalytics/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | *.sqlite3 5 | 6 | # Publishing 7 | /dist 8 | 9 | # Python 10 | /.venv 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # OS files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /forge-googleanalytics/forgegoogleanalytics/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-googleanalytics/forgegoogleanalytics/models.py -------------------------------------------------------------------------------- /forge-googleanalytics/forgegoogleanalytics/settings.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | from django.conf import settings 4 | 5 | 6 | def GOOGLEANALYTICS_MEASUREMENT_ID(): 7 | if "GOOGLEANALYTICS_MEASUREMENT_ID" in environ: 8 | return environ["GOOGLEANALYTICS_MEASUREMENT_ID"] 9 | 10 | return getattr(settings, "GOOGLEANALYTICS_MEASUREMENT_ID", None) 11 | 12 | 13 | def GOOGLEANALYTICS_API_SECRET(): 14 | if "GOOGLEANALYTICS_API_SECRET" in environ: 15 | return environ["GOOGLEANALYTICS_API_SECRET"] 16 | 17 | return getattr(settings, "GOOGLEANALYTICS_API_SECRET", None) 18 | -------------------------------------------------------------------------------- /forge-googleanalytics/forgegoogleanalytics/templates/googleanalytics/js.html: -------------------------------------------------------------------------------- 1 | {% if googleanalytics_measurement_id %} 2 | 3 | 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /forge-googleanalytics/forgegoogleanalytics/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-googleanalytics/forgegoogleanalytics/templatetags/__init__.py -------------------------------------------------------------------------------- /forge-googleanalytics/forgegoogleanalytics/templatetags/googleanalytics.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings as django_settings 3 | 4 | from .. import settings 5 | from ..utils import user_id_from_request 6 | 7 | register = template.Library() 8 | 9 | 10 | @register.inclusion_tag("googleanalytics/js.html", takes_context=True) 11 | def googleanalytics_js(context): 12 | if django_settings.DEBUG: 13 | return {} 14 | 15 | ctx = { 16 | "googleanalytics_measurement_id": settings.GOOGLEANALYTICS_MEASUREMENT_ID(), 17 | } 18 | 19 | if "request" in context: 20 | ctx["googleanalytics_user_id"] = user_id_from_request(context["request"]) 21 | 22 | return ctx 23 | -------------------------------------------------------------------------------- /forge-googleanalytics/forgegoogleanalytics/utils.py: -------------------------------------------------------------------------------- 1 | def user_id_from_request(request): 2 | # Analytics will be tied to the impersonator if we are one 3 | user = getattr(request, "impersonator", request.user) 4 | if user.is_authenticated: 5 | return user_id(user) 6 | return None 7 | 8 | 9 | def user_id(user): 10 | return user.pk 11 | -------------------------------------------------------------------------------- /forge-googleanalytics/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "forge-googleanalytics" 3 | packages = [ 4 | { include = "forgegoogleanalytics" }, 5 | ] 6 | 7 | version = "0.1.1" 8 | description = "" 9 | authors = ["Dave Gaeddert "] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | 14 | [tool.poetry.dev-dependencies] 15 | pytest = "^7.1.2" 16 | ipdb = "^0.13.9" 17 | Django = "^4.0.5" 18 | isort = "^5.10.1" 19 | black = "^22.6.0" 20 | pytest-django = "^4.5.2" 21 | 22 | [build-system] 23 | requires = ["poetry-core>=1.0.0"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /forge-googleanalytics/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgegoogleanalytics "$@" 3 | poetry run isort forgegoogleanalytics --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-googleanalytics/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-googleanalytics/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-googleanalytics/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | export DJANGO_SETTINGS_MODULE=tests.settings 3 | poetry run pytest tests "$@" 4 | -------------------------------------------------------------------------------- /forge-googleanalytics/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-googleanalytics/tests/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /forge-googleanalytics/tests/test_core.py: -------------------------------------------------------------------------------- 1 | def test_pass(): 2 | pass 3 | -------------------------------------------------------------------------------- /forge-googleanalytics/tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | urlpatterns = [ 5 | path("", TemplateView.as_view(template_name="index.html"), name="index"), 6 | ] 7 | -------------------------------------------------------------------------------- /forge-heroku/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-heroku/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-heroku/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Publishing 6 | /dist 7 | 8 | # Python 9 | /.venv 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # OS files 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /forge-heroku/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Dropseed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /forge-heroku/forgeheroku/__init__.py: -------------------------------------------------------------------------------- 1 | from .cli import cli 2 | 3 | __all__ = ["cli"] 4 | -------------------------------------------------------------------------------- /forge-heroku/forgeheroku/utils.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | 3 | SECRET_KEY_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)" 4 | 5 | 6 | def generate_secret_key(): 7 | return "".join(secrets.choice(SECRET_KEY_CHARS) for i in range(50)) 8 | -------------------------------------------------------------------------------- /forge-heroku/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgeheroku "$@" 3 | poetry run isort forgeheroku --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-heroku/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-heroku/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-heroku/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run pytest tests 3 | poetry run forge heroku --help 4 | poetry run forge-heroku --help 5 | -------------------------------------------------------------------------------- /forge-heroku/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from forgecore import Forge 2 | 3 | 4 | def test_import(): 5 | # Just make sure it loads for now 6 | forge = Forge() 7 | -------------------------------------------------------------------------------- /forge-htmx/.github/workflows/deps.yml: -------------------------------------------------------------------------------- 1 | name: deps 2 | 3 | on: 4 | schedule: 5 | - cron: 0 0 1 * * 6 | workflow_dispatch: {} 7 | 8 | jobs: 9 | deps: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - id: generate_token 13 | uses: tibdex/github-app-token@v1 14 | with: 15 | app_id: ${{ secrets.DEPS_GITHUB_APP_ID }} 16 | private_key: ${{ secrets.DEPS_GITHUB_APP_KEY }} 17 | - uses: actions/checkout@v3 18 | with: 19 | token: ${{ steps.generate_token.outputs.token }} 20 | - uses: actions/setup-python@v4 21 | with: 22 | python-version: 3.x 23 | - run: | 24 | pip install -U pip poetry 25 | ./scripts/install 26 | - run: curl https://deps.app/install.sh | bash -s -- -b $HOME/bin 27 | - run: $HOME/bin/deps ci 28 | env: 29 | DEPS_TOKEN: ${{ secrets.DEPS_TOKEN }} 30 | DEPS_GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 31 | -------------------------------------------------------------------------------- /forge-htmx/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-htmx/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-htmx/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Publishing 6 | /dist 7 | 8 | # Python 9 | /.venv 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # OS files 15 | .DS_Store 16 | 17 | # Node files 18 | node_modules/ 19 | -------------------------------------------------------------------------------- /forge-htmx/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Dropseed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /forge-htmx/deps.yml: -------------------------------------------------------------------------------- 1 | version: 3 2 | dependencies: 3 | - type: js 4 | path: . 5 | settings: 6 | before_commit: npm run copy-static 7 | - type: python 8 | path: poetry.lock 9 | -------------------------------------------------------------------------------- /forge-htmx/forgehtmx/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-htmx/forgehtmx/__init__.py -------------------------------------------------------------------------------- /forge-htmx/forgehtmx/templates/htmx/js.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | -------------------------------------------------------------------------------- /forge-htmx/forgehtmx/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-htmx/forgehtmx/templatetags/__init__.py -------------------------------------------------------------------------------- /forge-htmx/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forge-htmx", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "htmx.org": "1.9.6" 9 | } 10 | }, 11 | "node_modules/htmx.org": { 12 | "version": "1.9.6", 13 | "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.6.tgz", 14 | "integrity": "sha512-4Zebo9nzg8u2ZHuIJmvB/nQS6kIMLIoEfhTg/oRwyCIJhL5MLA/jPU1EPEBtGOmG4ZG0k05Vpd3sab2+zfvteQ==" 15 | } 16 | }, 17 | "dependencies": { 18 | "htmx.org": { 19 | "version": "1.9.6", 20 | "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.6.tgz", 21 | "integrity": "sha512-4Zebo9nzg8u2ZHuIJmvB/nQS6kIMLIoEfhTg/oRwyCIJhL5MLA/jPU1EPEBtGOmG4ZG0k05Vpd3sab2+zfvteQ==" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /forge-htmx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "copy-static": "cp node_modules/htmx.org/dist/htmx.min.js forgehtmx/static/htmx/htmx.min.js" 4 | }, 5 | "dependencies": { 6 | "htmx.org": "1.9.6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /forge-htmx/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | 3 | name = "forge-htmx" 4 | packages = [ 5 | { include = "forgehtmx" }, 6 | ] 7 | 8 | version = "0.4.0" 9 | description = "HTMX integration for Forge" 10 | authors = ["Dave Gaeddert "] 11 | license = "MIT" 12 | readme = "README.md" 13 | homepage = "https://www.forgepackages.com/" 14 | documentation = "https://www.forgepackages.com/docs/" 15 | repository = "https://github.com/forgepackages/forge-htmx" 16 | keywords = ["django", "saas", "forge", "framework"] 17 | classifiers = [ 18 | "Environment :: Web Environment", 19 | "Framework :: Django", 20 | "Framework :: Django :: 4", 21 | "Intended Audience :: Developers", 22 | "Operating System :: OS Independent", 23 | ] 24 | 25 | [tool.poetry.dependencies] 26 | python = "^3.8" 27 | 28 | [tool.poetry.group.dev.dependencies] 29 | pytest = "^7.1.2" 30 | pytest-django = "^4.5.2" 31 | ipdb = "^0.13.9" 32 | Django = "^4.0.5" 33 | black = "^23.1.0" 34 | isort = "^5.10.1" 35 | 36 | [build-system] 37 | requires = ["poetry-core>=1.0.0"] 38 | build-backend = "poetry.core.masonry.api" 39 | -------------------------------------------------------------------------------- /forge-htmx/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgehtmx "$@" 3 | poetry run isort forgehtmx --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-htmx/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | npm install 4 | -------------------------------------------------------------------------------- /forge-htmx/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-htmx/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | export DJANGO_SETTINGS_MODULE=tests.settings 3 | poetry run pytest tests "$@" 4 | -------------------------------------------------------------------------------- /forge-htmx/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-htmx/tests/settings.py: -------------------------------------------------------------------------------- 1 | INSTALLED_APPS = [ 2 | "forgehtmx", 3 | ] 4 | 5 | USE_TZ = True 6 | -------------------------------------------------------------------------------- /forge-htmx/tests/test_views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.views import View 3 | 4 | from forgehtmx.views import HTMXViewMixin 5 | 6 | 7 | class V(HTMXViewMixin, View): 8 | def get(self, request): 9 | return HttpResponse("Ok") 10 | 11 | 12 | def test_is_htmx_request(rf): 13 | request = rf.get("/", HTTP_HX_REQUEST="true") 14 | view = V() 15 | view.setup(request) 16 | assert view.is_htmx_request 17 | 18 | 19 | def test_fhx_fragment(rf): 20 | request = rf.get("/", HTTP_FHX_FRAGMENT="main") 21 | view = V() 22 | view.setup(request) 23 | assert view.htmx_fragment_name == "main" 24 | 25 | 26 | def test_fhx_action(rf): 27 | request = rf.get("/", HTTP_FHX_ACTION="create") 28 | view = V() 29 | view.setup(request) 30 | assert view.htmx_action_name == "create" 31 | -------------------------------------------------------------------------------- /forge-impersonate/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | with: 16 | prepare_cmd: | 17 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 18 | publish_cmd: | 19 | pip3 install -U pip poetry 20 | poetry build 21 | gh release upload $TAG dist/* 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-impersonate/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-impersonate/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | *.sqlite3 5 | 6 | # Publishing 7 | /dist 8 | 9 | # Python 10 | /.venv 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # OS files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/__init__.py: -------------------------------------------------------------------------------- 1 | from .middleware import ImpersonateMiddleware 2 | 3 | __all__ = ["ImpersonateMiddleware"] 4 | -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-impersonate/forgeimpersonate/models.py -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/permissions.py: -------------------------------------------------------------------------------- 1 | from . import settings 2 | 3 | 4 | def can_be_impersonator(user): 5 | return settings.IMPERSONATE_ALLOWED(user) 6 | 7 | 8 | def can_impersonate_user(impersonator, target_user): 9 | if not can_be_impersonator(impersonator): 10 | return False 11 | 12 | if target_user.is_superuser: 13 | # Nobody can impersonate superusers 14 | return False 15 | 16 | if target_user.is_staff and not impersonator.is_superuser: 17 | # Only superusers can impersonate other staff 18 | return False 19 | 20 | return True 21 | -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/settings.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def IMPERSONATE_ALLOWED(user): 5 | if hasattr(settings, "IMPERSONATE_ALLOWED"): 6 | return settings.IMPERSONATE_ALLOWED(user) 7 | 8 | return user.is_superuser or user.is_staff 9 | -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/templates/admin/auth/user/change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form.html" %} 2 | 3 | {% block object-tools-items %} 4 | {% include "impersonate/change_form_object_tools.html" %} 5 | {{ block.super }} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/templates/admin/users/user/change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form.html" %} 2 | 3 | {% block object-tools-items %} 4 | {% include "impersonate/change_form_object_tools.html" %} 5 | {{ block.super }} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/templates/impersonate/change_form_object_tools.html: -------------------------------------------------------------------------------- 1 | {% load impersonate %} 2 | {% if request.user|can_impersonate_user:original %} 3 |
  • 4 | Impersonate 5 |
  • 6 | {% endif %} 7 | -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-impersonate/forgeimpersonate/templatetags/__init__.py -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/templatetags/impersonate.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | from ..permissions import can_impersonate_user as _can_impersonate_user 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.filter 9 | def can_impersonate_user(user, target_user): 10 | return _can_impersonate_user(user, target_user) 11 | -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import ImpersonateStartView, ImpersonateStopView 4 | 5 | app_name = "impersonate" 6 | 7 | urlpatterns = [ 8 | path("stop/", ImpersonateStopView.as_view(), name="stop"), 9 | path("start//", ImpersonateStartView.as_view(), name="start"), 10 | ] 11 | -------------------------------------------------------------------------------- /forge-impersonate/forgeimpersonate/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponseForbidden, HttpResponseRedirect 2 | from django.views import View 3 | 4 | from .permissions import can_be_impersonator 5 | 6 | IMPERSONATE_KEY = "impersonate" 7 | 8 | 9 | class ImpersonateStartView(View): 10 | def get(self, request, *args, **kwargs): 11 | # We *could* already be impersonating, so need to consider that 12 | impersonator = getattr(request, "impersonator", request.user) 13 | if impersonator and can_be_impersonator(impersonator): 14 | request.session[IMPERSONATE_KEY] = kwargs["pk"] 15 | return HttpResponseRedirect(request.GET.get("next", "/")) 16 | 17 | return HttpResponseForbidden() 18 | 19 | 20 | class ImpersonateStopView(View): 21 | def get(self, request, *args, **kwargs): 22 | request.session.pop(IMPERSONATE_KEY) 23 | return HttpResponseRedirect(request.GET.get("next", "/")) 24 | -------------------------------------------------------------------------------- /forge-impersonate/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "forge-impersonate" 3 | packages = [ 4 | { include = "forgeimpersonate" }, 5 | ] 6 | 7 | version = "0.1.0" 8 | description = "" 9 | authors = ["Dave Gaeddert "] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | 14 | [tool.poetry.dev-dependencies] 15 | pytest = "^7.1.2" 16 | ipdb = "^0.13.9" 17 | Django = "^4.0.5" 18 | isort = "^5.10.1" 19 | black = "^22.6.0" 20 | pytest-django = "^4.5.2" 21 | 22 | [build-system] 23 | requires = ["poetry-core>=1.0.0"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /forge-impersonate/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgeimpersonate "$@" 3 | poetry run isort forgeimpersonate --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-impersonate/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-impersonate/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-impersonate/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | export DJANGO_SETTINGS_MODULE=tests.settings 3 | poetry run pytest tests "$@" 4 | -------------------------------------------------------------------------------- /forge-impersonate/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-impersonate/tests/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /forge-impersonate/tests/test_core.py: -------------------------------------------------------------------------------- 1 | def test_pass(): 2 | pass 3 | -------------------------------------------------------------------------------- /forge-impersonate/tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | urlpatterns = [ 5 | path("", TemplateView.as_view(template_name="index.html"), name="index"), 6 | ] 7 | -------------------------------------------------------------------------------- /forge-importmap/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-importmap/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.8", "3.9", "3.10"] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-python@v2 14 | with: 15 | python-version: ${{ matrix.python-version }} 16 | - name: Install dependencies 17 | run: | 18 | pip install poetry 19 | ./scripts/install 20 | - name: Check formatting and test 21 | run: | 22 | ./scripts/pre-commit 23 | -------------------------------------------------------------------------------- /forge-importmap/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /.venv 3 | *.pyc 4 | __pycache__ 5 | db.sqlite3 6 | -------------------------------------------------------------------------------- /forge-importmap/forgeimportmap/__init__.py: -------------------------------------------------------------------------------- 1 | from .cli import cli 2 | 3 | __all__ = ["cli"] 4 | -------------------------------------------------------------------------------- /forge-importmap/forgeimportmap/cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from .core import Importmap 4 | 5 | 6 | @click.group() 7 | def cli(): 8 | pass 9 | 10 | 11 | @cli.command() 12 | def generate(): 13 | """Generate importmap.lock""" 14 | importmap = Importmap() 15 | importmap.load() 16 | -------------------------------------------------------------------------------- /forge-importmap/forgeimportmap/templates/importmap/scripts.html: -------------------------------------------------------------------------------- 1 | {% if importmap %} 2 | 3 | 6 | {% endif %} 7 | -------------------------------------------------------------------------------- /forge-importmap/forgeimportmap/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-importmap/forgeimportmap/templatetags/__init__.py -------------------------------------------------------------------------------- /forge-importmap/forgeimportmap/templatetags/importmap.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django import template 4 | from django.conf import settings 5 | 6 | from ..core import Importmap 7 | 8 | register = template.Library() 9 | 10 | 11 | @register.inclusion_tag("importmap/scripts.html") 12 | def importmap_scripts(): 13 | importmap = Importmap() 14 | importmap.load() 15 | 16 | if settings.DEBUG: 17 | return {"importmap": json.dumps(importmap.map_dev, indent=2, sort_keys=True)} 18 | else: 19 | return {"importmap": json.dumps(importmap.map, sort_keys=True)} 20 | -------------------------------------------------------------------------------- /forge-importmap/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | ./.venv/bin/black forgeimportmap "$@" 3 | ./.venv/bin/black test_project "$@" 4 | ./.venv/bin/isort --profile=black forgeimportmap "$@" 5 | ./.venv/bin/isort --profile=black test_project "$@" 6 | -------------------------------------------------------------------------------- /forge-importmap/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-importmap/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | ./scripts/format --check 3 | ./scripts/test 4 | -------------------------------------------------------------------------------- /forge-importmap/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd test_project 3 | ../.venv/bin/python manage.py test "$@" 4 | poetry run forge importmap --help 5 | poetry run forge-importmap --help 6 | -------------------------------------------------------------------------------- /forge-importmap/test_project/app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AppConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "app" 7 | -------------------------------------------------------------------------------- /forge-importmap/test_project/app/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-importmap/test_project/app/models.py -------------------------------------------------------------------------------- /forge-importmap/test_project/app/static/app.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | console.log(React); 4 | -------------------------------------------------------------------------------- /forge-importmap/test_project/app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% load static importmap %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | {% importmap_scripts %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /forge-importmap/test_project/app/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | urlpatterns = [ 5 | path("", TemplateView.as_view(template_name="index.html"), name="index"), 6 | ] 7 | -------------------------------------------------------------------------------- /forge-importmap/test_project/importmap.lock: -------------------------------------------------------------------------------- 1 | { 2 | "importmap_dev": { 3 | "imports": { 4 | "react": "https://ga.jspm.io/npm:react@17.0.2/dev.index.js" 5 | }, 6 | "scopes": { 7 | "https://ga.jspm.io/": { 8 | "object-assign": "https://ga.jspm.io/npm:object-assign@4.1.1/index.js" 9 | } 10 | } 11 | }, 12 | "importmap": { 13 | "imports": { 14 | "react": "https://ga.jspm.io/npm:react@17.0.2/index.js" 15 | }, 16 | "scopes": { 17 | "https://ga.jspm.io/": { 18 | "object-assign": "https://ga.jspm.io/npm:object-assign@4.1.1/index.js" 19 | } 20 | } 21 | }, 22 | "config_hash": "09d6237cdd891aad07de60f54689d130" 23 | } -------------------------------------------------------------------------------- /forge-importmap/test_project/importmap.toml: -------------------------------------------------------------------------------- 1 | [[packages]] 2 | name = "react" 3 | source = "react@17.0.2" 4 | -------------------------------------------------------------------------------- /forge-importmap/test_project/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-importmap/test_project/tests.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.test import Client, TestCase 3 | from django.urls import reverse 4 | 5 | 6 | class TestTemplate(TestCase): 7 | def setUp(self): 8 | self.client = Client() 9 | 10 | def test_template_output(self): 11 | settings.DEBUG = False 12 | 13 | url = reverse("index") 14 | response = self.client.get(url) 15 | 16 | self.assertEqual(response.status_code, 200) 17 | self.assertTemplateUsed(response, "index.html") 18 | self.assertContains(response, "es-module-shims.js") 19 | 20 | self.assertContains(response, "react@17.0.2/index.js") 21 | 22 | def test_template_output_dev(self): 23 | settings.DEBUG = True 24 | 25 | url = reverse("index") 26 | response = self.client.get(url) 27 | # print(response.content.decode()) 28 | 29 | self.assertEqual(response.status_code, 200) 30 | self.assertTemplateUsed(response, "index.html") 31 | self.assertContains(response, "es-module-shims.js") 32 | 33 | self.assertContains(response, "react@17.0.2/dev.index.js") 34 | -------------------------------------------------------------------------------- /forge-importmap/test_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for 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/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /forge-logging/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-logging/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-logging/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Publishing 6 | /dist 7 | 8 | # Python 9 | /.venv 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # OS files 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /forge-logging/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Dropseed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /forge-logging/README.md: -------------------------------------------------------------------------------- 1 | # forge-logging 2 | -------------------------------------------------------------------------------- /forge-logging/forgelogging/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import app_logger 2 | 3 | __all__ = ["app_logger"] 4 | -------------------------------------------------------------------------------- /forge-logging/forgelogging/filters.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from django.conf import settings 3 | 4 | 5 | class ExcludeCommonURLsFilter(logging.Filter): 6 | def __init__(self): 7 | self.exclude_urls = [ 8 | "/admin/jsi18n/", # could be customized... 9 | "/favicon.ico", 10 | ] 11 | 12 | if settings.STATIC_URL: 13 | self.exclude_urls.append(settings.STATIC_URL) 14 | 15 | super().__init__() 16 | 17 | def filter(self, record): 18 | message = record.getMessage() 19 | 20 | if message.startswith('"GET /'): # Only check them all if it's a GET 21 | for url in self.exclude_urls: 22 | if message.startswith(f'"GET {url}'): 23 | return False 24 | 25 | return True 26 | -------------------------------------------------------------------------------- /forge-logging/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | 3 | name = "forge-logging" 4 | packages = [ 5 | { include = "forgelogging" }, 6 | ] 7 | 8 | version = "1.0.0" 9 | description = "Logging for Forge" 10 | authors = ["Dave Gaeddert "] 11 | license = "MIT" 12 | readme = "README.md" 13 | homepage = "https://www.forgepackages.com/" 14 | documentation = "https://www.forgepackages.com/docs/" 15 | repository = "https://github.com/forgepackages/forge-logging" 16 | keywords = ["django", "saas", "forge", "framework"] 17 | classifiers = [ 18 | "Environment :: Web Environment", 19 | "Framework :: Django", 20 | "Framework :: Django :: 4", 21 | "Intended Audience :: Developers", 22 | "Operating System :: OS Independent", 23 | ] 24 | 25 | [tool.poetry.dependencies] 26 | python = "^3.8" 27 | forge-core = "^1.0.0" 28 | 29 | [tool.poetry.dev-dependencies] 30 | pytest = "^7.1.2" 31 | ipdb = "^0.13.9" 32 | Django = "^4.0.5" 33 | 34 | [build-system] 35 | requires = ["poetry-core>=1.0.0"] 36 | build-backend = "poetry.core.masonry.api" 37 | -------------------------------------------------------------------------------- /forge-logging/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgelogging "$@" 3 | poetry run black tests "$@" 4 | -------------------------------------------------------------------------------- /forge-logging/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-logging/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-logging/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run pytest tests 3 | -------------------------------------------------------------------------------- /forge-logging/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-logging/tests/settings.py: -------------------------------------------------------------------------------- 1 | INSTALLED_APPS = [ 2 | "forgelogging", 3 | ] 4 | -------------------------------------------------------------------------------- /forge-logging/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from forgecore import Forge 2 | 3 | 4 | def test_import(): 5 | # Just make sure it loads for now 6 | forge = Forge() 7 | -------------------------------------------------------------------------------- /forge-oauth/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | with: 16 | prepare_cmd: | 17 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 18 | publish_cmd: | 19 | pip3 install -U pip poetry 20 | poetry build 21 | gh release upload $TAG dist/* 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-oauth/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.8", "3.9", "3.10"] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-python@v2 14 | with: 15 | python-version: ${{ matrix.python-version }} 16 | - name: Install dependencies 17 | run: | 18 | pip install poetry 19 | ./scripts/install 20 | - name: Format check 21 | run: | 22 | ./scripts/pre-commit 23 | - name: Test 24 | run: | 25 | ./scripts/test 26 | -------------------------------------------------------------------------------- /forge-oauth/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist 3 | /.venv 4 | *.pyc 5 | __pycache__ 6 | db.sqlite3 7 | .env 8 | -------------------------------------------------------------------------------- /forge-oauth/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dropseed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /forge-oauth/forgeoauth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-oauth/forgeoauth/__init__.py -------------------------------------------------------------------------------- /forge-oauth/forgeoauth/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import OAuthConnection 4 | 5 | 6 | @admin.register(OAuthConnection) 7 | class OAuthConnectionAdmin(admin.ModelAdmin): 8 | list_display = ("user", "provider_key", "provider_user_id", "created_at") 9 | search_fields = ( 10 | "user__email", 11 | "provider_user_id", 12 | ) 13 | list_filter = ("provider_key",) 14 | -------------------------------------------------------------------------------- /forge-oauth/forgeoauth/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ForgeOAuthConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "forgeoauth" 7 | -------------------------------------------------------------------------------- /forge-oauth/forgeoauth/exceptions.py: -------------------------------------------------------------------------------- 1 | class OAuthError(Exception): 2 | """Base class for OAuth errors""" 3 | 4 | pass 5 | 6 | 7 | class OAuthStateMismatchError(OAuthError): 8 | pass 9 | 10 | 11 | class OAuthCannotDisconnectError(OAuthError): 12 | pass 13 | 14 | 15 | class OAuthUserAlreadyExistsError(OAuthError): 16 | pass 17 | -------------------------------------------------------------------------------- /forge-oauth/forgeoauth/migrations/0002_alter_oauthconnection_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.3 on 2022-03-18 18:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("forgeoauth", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="oauthconnection", 15 | options={"ordering": ("provider_key",), "verbose_name": "OAuth Connection"}, 16 | ), 17 | migrations.AlterField( 18 | model_name="oauthconnection", 19 | name="access_token", 20 | field=models.CharField(max_length=100), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /forge-oauth/forgeoauth/migrations/0003_alter_oauthconnection_access_token_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.6 on 2022-08-11 19:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("forgeoauth", "0002_alter_oauthconnection_options_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="oauthconnection", 15 | name="access_token", 16 | field=models.CharField(max_length=300), 17 | ), 18 | migrations.AlterField( 19 | model_name="oauthconnection", 20 | name="refresh_token", 21 | field=models.CharField(blank=True, max_length=300), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /forge-oauth/forgeoauth/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-oauth/forgeoauth/migrations/__init__.py -------------------------------------------------------------------------------- /forge-oauth/forgeoauth/templates/oauth/error.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

    OAuth Error

    5 |

    {{ oauth_error }}

    6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /forge-oauth/forgeoauth/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | 3 | from . import views 4 | 5 | app_name = "forgeoauth" 6 | 7 | urlpatterns = [ 8 | path( 9 | "/", 10 | include( 11 | [ 12 | # Login and Signup are both handled here, because the intent is the same 13 | path("login/", views.OAuthLoginView.as_view(), name="login"), 14 | path("connect/", views.OAuthConnectView.as_view(), name="connect"), 15 | path( 16 | "disconnect/", 17 | views.OAuthDisconnectView.as_view(), 18 | name="disconnect", 19 | ), 20 | path("callback/", views.OAuthCallbackView.as_view(), name="callback"), 21 | ] 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /forge-oauth/provider_examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-oauth/provider_examples/__init__.py -------------------------------------------------------------------------------- /forge-oauth/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | ./.venv/bin/black forgeoauth "$@" 3 | ./.venv/bin/isort --profile black forgeoauth "$@" 4 | ./.venv/bin/black tests "$@" 5 | ./.venv/bin/isort --profile black tests "$@" 6 | -------------------------------------------------------------------------------- /forge-oauth/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-oauth/scripts/makemigrations: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | ./.venv/bin/python tests/manage.py makemigrations 3 | -------------------------------------------------------------------------------- /forge-oauth/scripts/mypy: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | ./.venv/bin/mypy forgeoauth --ignore-missing-imports "$@" 3 | -------------------------------------------------------------------------------- /forge-oauth/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | ./scripts/format --check 3 | ./scripts/mypy 4 | ./scripts/test 5 | -------------------------------------------------------------------------------- /forge-oauth/scripts/runserver: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | rm -f tests/db.sqlite3 3 | ./.venv/bin/python tests/manage.py migrate 4 | ./.venv/bin/python tests/manage.py runserver 5 | -------------------------------------------------------------------------------- /forge-oauth/scripts/shell: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | ./.venv/bin/python tests/manage.py shell 3 | -------------------------------------------------------------------------------- /forge-oauth/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | ./.venv/bin/pytest tests "$@" 3 | -------------------------------------------------------------------------------- /forge-oauth/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-oauth/tests/__init__.py -------------------------------------------------------------------------------- /forge-oauth/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-oauth/tests/provider_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-oauth/tests/provider_tests/__init__.py -------------------------------------------------------------------------------- /forge-oauth/tests/providers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-oauth/tests/providers/__init__.py -------------------------------------------------------------------------------- /forge-oauth/tests/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | {% block content %} 11 | {% endblock %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /forge-oauth/tests/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

    Login

    5 |
    6 | {% csrf_token %} 7 | 8 |
    9 |
    10 | {% csrf_token %} 11 | 12 |
    13 |
    14 | {% csrf_token %} 15 | 16 |
    17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /forge-oauth/tests/test_models.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from forgeoauth.models import OAuthConnection 4 | from forgeoauth.providers import OAuthToken, OAuthUser 5 | 6 | 7 | @pytest.mark.django_db 8 | def test_email_normalized(): 9 | """Make sure that the normalize_email function is being called somewhere in here (was concerned it wasn't)""" 10 | connection = OAuthConnection.get_or_createuser( 11 | provider_key="dummy", 12 | oauth_token=OAuthToken( 13 | access_token="dummy_access_token", 14 | ), 15 | oauth_user=OAuthUser( 16 | id="dummy_id", 17 | username="dummy_username", 18 | email="Dummy@ExAmPlE.com", 19 | ), 20 | ) 21 | assert connection.user.email == "Dummy@example.com" 22 | -------------------------------------------------------------------------------- /forge-oauth/tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.mixins import LoginRequiredMixin 3 | from django.contrib.auth.views import LogoutView 4 | from django.urls import include, path 5 | from django.views.generic import TemplateView 6 | 7 | from forgeoauth.providers import get_provider_keys 8 | 9 | 10 | class LoggedInView(LoginRequiredMixin, TemplateView): 11 | template_name = "index.html" 12 | 13 | def get_context_data(self, **kwargs): 14 | context = super().get_context_data(**kwargs) 15 | context["oauth_provider_keys"] = get_provider_keys() 16 | return context 17 | 18 | 19 | class LoginView(TemplateView): 20 | template_name = "login.html" 21 | 22 | 23 | urlpatterns = [ 24 | path("admin", admin.site.urls), 25 | path("oauth/", include("forgeoauth.urls")), 26 | path("login/", LoginView.as_view(), name="login"), 27 | path("logout/", LogoutView.as_view(), name="logout"), 28 | path("", LoggedInView.as_view()), 29 | ] 30 | -------------------------------------------------------------------------------- /forge-oauth/tests/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-oauth/tests/users/__init__.py -------------------------------------------------------------------------------- /forge-oauth/tests/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "users" 7 | -------------------------------------------------------------------------------- /forge-oauth/tests/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-oauth/tests/users/migrations/__init__.py -------------------------------------------------------------------------------- /forge-oauth/tests/users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | 4 | 5 | class User(AbstractUser): 6 | # Make email unique (is isn't by default) 7 | email = models.EmailField(unique=True) 8 | -------------------------------------------------------------------------------- /forge-precommit/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-precommit/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-precommit/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Publishing 6 | /dist 7 | 8 | # Python 9 | /.venv 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # OS files 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /forge-precommit/README.md: -------------------------------------------------------------------------------- 1 | # forge-precommit 2 | -------------------------------------------------------------------------------- /forge-precommit/forgeprecommit/__init__.py: -------------------------------------------------------------------------------- 1 | from .cli import cli 2 | 3 | __all__ = ["cli"] 4 | -------------------------------------------------------------------------------- /forge-precommit/forgeprecommit/install.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from forgecore import Forge 5 | 6 | 7 | def install_git_hook(): 8 | forge = Forge() 9 | 10 | if not forge.repo_root: 11 | print("Not in a git repository") 12 | sys.exit(1) 13 | 14 | hook_path = os.path.join(forge.repo_root, ".git", "hooks", "pre-commit") 15 | if os.path.exists(hook_path): 16 | print("pre-commit hook already exists") 17 | else: 18 | with open(hook_path, "w") as f: 19 | f.write( 20 | """#!/bin/sh 21 | forge pre-commit""" 22 | ) 23 | os.chmod(hook_path, 0o755) 24 | print("pre-commit hook installed") 25 | -------------------------------------------------------------------------------- /forge-precommit/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgeprecommit "$@" 3 | poetry run isort forgeprecommit --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-precommit/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-precommit/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-precommit/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run pytest tests 3 | poetry run forge pre-commit --help 4 | poetry run forge-pre-commit --help 5 | -------------------------------------------------------------------------------- /forge-precommit/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-precommit/tests/settings.py: -------------------------------------------------------------------------------- 1 | INSTALLED_APPS = [ 2 | "forgeprecommit", 3 | ] 4 | -------------------------------------------------------------------------------- /forge-precommit/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from forgecore import Forge 2 | 3 | 4 | def test_import(): 5 | # Just make sure it loads for now 6 | forge = Forge() 7 | -------------------------------------------------------------------------------- /forge-pro/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | with: 16 | prepare_cmd: | 17 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 18 | publish_cmd: | 19 | pip3 install -U pip poetry 20 | poetry build 21 | gh release upload $TAG dist/* 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-pro/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | - run: pipx install poetry 12 | - uses: actions/setup-python@v4 13 | with: 14 | python-version: "3.9" 15 | cache: poetry 16 | - name: Install dependencies 17 | env: 18 | POETRY_HTTP_BASIC_FORGEPACKAGES_USERNAME: ${{ secrets.FORGEPACKAGES_PROJECT }} 19 | POETRY_HTTP_BASIC_FORGEPACKAGES_PASSWORD: ${{ secrets.FORGEPACKAGES_TOKEN }} 20 | run: poetry install 21 | - name: Test 22 | run: ./scripts/test 23 | -------------------------------------------------------------------------------- /forge-pro/.gitignore: -------------------------------------------------------------------------------- 1 | /.env 2 | __pycache__ 3 | .DS_Store 4 | *.sqlite3 5 | /dist 6 | -------------------------------------------------------------------------------- /forge-pro/LICENSE: -------------------------------------------------------------------------------- 1 | Forge Pro is not open source. Please refer to the license agreed to at https://billing.forgepackages.com/. 2 | -------------------------------------------------------------------------------- /forge-pro/forgepro/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-pro/forgepro/__init__.py -------------------------------------------------------------------------------- /forge-pro/forgepro/default_settings.py: -------------------------------------------------------------------------------- 1 | FORGEPRO_APPS = [ 2 | "forgesentry", 3 | "forgestripe", 4 | "forgestafftoolbar", 5 | "forgeimpersonate", 6 | "forgegoogleanalytics", 7 | "forgerequestlog", 8 | "forgequerystats", 9 | ] 10 | 11 | FORGEPRO_MIDDLEWARE = [ 12 | "forgequerystats.QueryStatsMiddleware", 13 | "forgesentry.SentryFeedbackMiddleware", 14 | "forgeimpersonate.ImpersonateMiddleware", 15 | "forgerequestlog.RequestLogMiddleware", 16 | ] 17 | -------------------------------------------------------------------------------- /forge-pro/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "forge-pro" 3 | version = "0.2.0" 4 | authors = ["Dave Gaeddert "] 5 | readme = "README.md" 6 | description = "" 7 | packages = [ 8 | { include = "forgepro" }, 9 | ] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | forge-stafftoolbar = "<1.0.0" 14 | forge-stripe = "<1.0.0" 15 | forge-sentry = "<1.0.0" 16 | forge-requestlog = "<1.0.0" 17 | forge-querystats = "<1.0.0" 18 | forge-impersonate = "<1.0.0" 19 | forge-googleanalytics = "<1.0.0" 20 | 21 | [tool.poetry.dev-dependencies] 22 | # Forge includes pytest, black, etc. 23 | forge = "<1.0.0" 24 | 25 | [build-system] 26 | requires = ["poetry-core>=1.0.0"] 27 | build-backend = "poetry.core.masonry.api" 28 | 29 | [[tool.poetry.source]] 30 | name = "forgepackages" 31 | url = "https://pypi.forgepackages.com/" 32 | -------------------------------------------------------------------------------- /forge-pro/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgepro --exclude migrations "$@" 3 | poetry run isort forgepro --skip migrations --profile black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile black "$@" 7 | -------------------------------------------------------------------------------- /forge-pro/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-pro/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-pro/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | export PYTHONPATH="$(pwd):$(pwd)/tests" 3 | export DJANGO_SETTINGS_MODULE=settings 4 | export SECRET_KEY=test 5 | export DATABASE_URL=sqlite:///tests/db.sqlite3 6 | export BASE_URL=http://localhost:8000 7 | export DEBUG=true 8 | 9 | cd tests 10 | poetry run pytest . "$@" 11 | -------------------------------------------------------------------------------- /forge-pro/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-pro/tests/__init__.py -------------------------------------------------------------------------------- /forge-pro/tests/settings.py: -------------------------------------------------------------------------------- 1 | from forge.default_settings import * 2 | 3 | from forgepro.default_settings import FORGEPRO_APPS, FORGEPRO_MIDDLEWARE 4 | 5 | INSTALLED_APPS = INSTALLED_APPS + FORGEPRO_APPS 6 | 7 | MIDDLEWARE = MIDDLEWARE + FORGEPRO_MIDDLEWARE 8 | 9 | AUTH_USER_MODEL = "auth.User" 10 | -------------------------------------------------------------------------------- /forge-pro/tests/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load sentry %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | {% sentry_js %} 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /forge-pro/tests/test_core.py: -------------------------------------------------------------------------------- 1 | def test_core(): 2 | print("Apps loaded successfully") 3 | -------------------------------------------------------------------------------- /forge-pro/tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | 5 | class ErrorView(TemplateView): 6 | template_name = "base.html" 7 | 8 | def get_context_data(self, **kwargs): 9 | raise Exception("Test!") 10 | 11 | 12 | urlpatterns = [ 13 | path("error/", ErrorView.as_view()), 14 | ] 15 | -------------------------------------------------------------------------------- /forge-querystats/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | with: 16 | prepare_cmd: | 17 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 18 | publish_cmd: | 19 | pip3 install -U pip poetry 20 | poetry build 21 | gh release upload $TAG dist/* 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-querystats/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-querystats/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | *.sqlite3 5 | 6 | # Publishing 7 | /dist 8 | 9 | # Python 10 | /.venv 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # OS files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /forge-querystats/forgequerystats/__init__.py: -------------------------------------------------------------------------------- 1 | from .middleware import QueryStatsMiddleware 2 | 3 | __all__ = ["QueryStatsMiddleware"] 4 | -------------------------------------------------------------------------------- /forge-querystats/forgequerystats/sql.py: -------------------------------------------------------------------------------- 1 | import sqlparse 2 | 3 | 4 | def pretty_print_sql(sql): 5 | return sqlparse.format(sql, reindent=True, keyword_case="upper") 6 | -------------------------------------------------------------------------------- /forge-querystats/forgequerystats/templates/querystats/stafftoolbar.html: -------------------------------------------------------------------------------- 1 | {% include "querystats/button.html" %} 2 | -------------------------------------------------------------------------------- /forge-querystats/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "forge-querystats" 3 | packages = [ 4 | { include = "forgequerystats" }, 5 | ] 6 | 7 | version = "0.3.0" 8 | description = "" 9 | authors = ["Dave Gaeddert "] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | sqlparse = ">=0.2.2" 14 | 15 | [tool.poetry.dev-dependencies] 16 | pytest = "^7.1.2" 17 | ipdb = "^0.13.9" 18 | Django = "^4.0.5" 19 | isort = "^5.10.1" 20 | black = "^22.6.0" 21 | pytest-django = "^4.5.2" 22 | 23 | [build-system] 24 | requires = ["poetry-core>=1.0.0"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /forge-querystats/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgequerystats "$@" 3 | poetry run isort forgequerystats --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-querystats/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-querystats/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-querystats/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | export DJANGO_SETTINGS_MODULE=tests.settings 3 | poetry run pytest tests "$@" 4 | -------------------------------------------------------------------------------- /forge-querystats/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-querystats/tests/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /forge-querystats/tests/test_sql.py: -------------------------------------------------------------------------------- 1 | from forgequerystats.sql import pretty_print_sql 2 | 3 | 4 | def test_pretty_print_sql(): 5 | sql = "SELECT * FROM foo WHERE bar = 'baz' ORDER BY baz LIMIT 10" 6 | assert pretty_print_sql(sql) == ( 7 | "SELECT *\n" "FROM foo\n" "WHERE bar = 'baz'\n" "ORDER BY baz\n" "LIMIT 10" 8 | ) 9 | -------------------------------------------------------------------------------- /forge-querystats/tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | urlpatterns = [ 5 | path("", TemplateView.as_view(template_name="index.html"), name="index"), 6 | ] 7 | -------------------------------------------------------------------------------- /forge-requestlog/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | with: 16 | prepare_cmd: | 17 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 18 | publish_cmd: | 19 | pip3 install -U pip poetry 20 | poetry build 21 | gh release upload $TAG dist/* 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-requestlog/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-requestlog/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | *.sqlite3 5 | 6 | # Publishing 7 | /dist 8 | 9 | # Python 10 | /.venv 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # OS files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /forge-requestlog/forgerequestlog/__init__.py: -------------------------------------------------------------------------------- 1 | from .middleware import RequestLogMiddleware 2 | 3 | __all__ = ["RequestLogMiddleware"] 4 | -------------------------------------------------------------------------------- /forge-requestlog/forgerequestlog/settings.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def REQUESTLOG_IGNORE_URL_PATHS(): 5 | return getattr( 6 | settings, 7 | "REQUESTLOG_IGNORE_URL_PATHS", 8 | [ 9 | "/favicon.ico", 10 | "/favicon.ico/", 11 | "/admin/jsi18n/", 12 | ], 13 | ) 14 | 15 | 16 | def REQUESTLOG_KEEP_LATEST(): 17 | return getattr(settings, "REQUESTLOG_KEEP_LATEST", 50) 18 | 19 | 20 | def REQUESTLOG_URL(): 21 | return getattr(settings, "REQUESTLOG_URL", "/requestlog/") 22 | -------------------------------------------------------------------------------- /forge-requestlog/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "forge-requestlog" 3 | packages = [ 4 | { include = "forgerequestlog" }, 5 | ] 6 | 7 | version = "0.2.0" 8 | description = "" 9 | authors = ["Dave Gaeddert "] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | requests = ">=2.0.0" 14 | forge-core = "*" 15 | 16 | [tool.poetry.dev-dependencies] 17 | pytest = "^7.1.2" 18 | ipdb = "^0.13.9" 19 | Django = "^4.0.5" 20 | isort = "^5.10.1" 21 | black = "^22.6.0" 22 | pytest-django = "^4.5.2" 23 | 24 | [build-system] 25 | requires = ["poetry-core>=1.0.0"] 26 | build-backend = "poetry.core.masonry.api" 27 | -------------------------------------------------------------------------------- /forge-requestlog/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgerequestlog "$@" 3 | poetry run isort forgerequestlog --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-requestlog/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-requestlog/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-requestlog/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | export DJANGO_SETTINGS_MODULE=tests.settings 3 | poetry run pytest tests "$@" 4 | -------------------------------------------------------------------------------- /forge-requestlog/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-requestlog/tests/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /forge-requestlog/tests/test_core.py: -------------------------------------------------------------------------------- 1 | def test_pass(): 2 | pass 3 | -------------------------------------------------------------------------------- /forge-requestlog/tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | urlpatterns = [ 5 | path("", TemplateView.as_view(template_name="index.html"), name="index"), 6 | ] 7 | -------------------------------------------------------------------------------- /forge-sentry/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | with: 16 | prepare_cmd: | 17 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 18 | publish_cmd: | 19 | pip3 install -U pip poetry 20 | poetry build 21 | gh release upload $TAG dist/* 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-sentry/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-sentry/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | *.sqlite3 5 | 6 | # Publishing 7 | /dist 8 | 9 | # Python 10 | /.venv 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # OS files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /forge-sentry/forgesentry/__init__.py: -------------------------------------------------------------------------------- 1 | from .middleware import SentryFeedbackMiddleware 2 | 3 | __all__ = ["SentryFeedbackMiddleware"] 4 | -------------------------------------------------------------------------------- /forge-sentry/forgesentry/apps.py: -------------------------------------------------------------------------------- 1 | import sentry_sdk 2 | from django.apps import AppConfig 3 | from sentry_sdk.integrations.django import DjangoIntegration 4 | 5 | from . import settings 6 | 7 | 8 | class ForgesentryConfig(AppConfig): 9 | default_auto_field = "django.db.models.BigAutoField" 10 | name = "forgesentry" 11 | 12 | def ready(self): 13 | if settings.SENTRY_DSN(): 14 | sentry_sdk.init( 15 | settings.SENTRY_DSN(), 16 | release=settings.SENTRY_RELEASE(), 17 | environment=settings.SENTRY_ENVIRONMENT(), 18 | send_default_pii=settings.SENTRY_PII_ENABLED(), 19 | traces_sample_rate=settings.SENTRY_TRACES_SAMPLE_RATE(), 20 | integrations=[DjangoIntegration()], 21 | **settings.SENTRY_INIT_KWARGS(), 22 | ) 23 | -------------------------------------------------------------------------------- /forge-sentry/forgesentry/templates/sentry/js.html: -------------------------------------------------------------------------------- 1 | {% if sentry_js_enabled %} 2 | 3 | {{ sentry_init|json_script:"sentry_init" }} 4 | 13 | {% endif %} 14 | -------------------------------------------------------------------------------- /forge-sentry/forgesentry/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-sentry/forgesentry/templatetags/__init__.py -------------------------------------------------------------------------------- /forge-sentry/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "forge-sentry" 3 | packages = [ 4 | { include = "forgesentry" }, 5 | ] 6 | 7 | version = "0.3.0" 8 | description = "" 9 | authors = ["Dave Gaeddert "] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | sentry-sdk = "^1.8.0" 14 | 15 | [tool.poetry.dev-dependencies] 16 | pytest = "^7.1.2" 17 | ipdb = "^0.13.9" 18 | Django = "^4.0.5" 19 | isort = "^5.10.1" 20 | black = "^22.6.0" 21 | pytest-django = "^4.5.2" 22 | 23 | [build-system] 24 | requires = ["poetry-core>=1.0.0"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /forge-sentry/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgesentry "$@" 3 | poetry run isort forgesentry --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-sentry/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-sentry/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-sentry/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | export DJANGO_SETTINGS_MODULE=tests.settings 3 | poetry run pytest tests "$@" 4 | -------------------------------------------------------------------------------- /forge-sentry/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-sentry/tests/templates/index.html: -------------------------------------------------------------------------------- 1 | {% load sentry %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | {% sentry_js %} 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /forge-sentry/tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | 5 | class ErrorView(TemplateView): 6 | template_name = "index.html" # Won't actually render this, will error instead 7 | 8 | def get_context_data(self, **kwargs): 9 | raise Exception("Test!") 10 | 11 | 12 | urlpatterns = [ 13 | path("error/", ErrorView.as_view()), 14 | path("", TemplateView.as_view(template_name="index.html"), name="index"), 15 | ] 16 | -------------------------------------------------------------------------------- /forge-stafftoolbar/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | with: 16 | prepare_cmd: | 17 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 18 | publish_cmd: | 19 | pip3 install -U pip poetry 20 | poetry build 21 | gh release upload $TAG dist/* 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-stafftoolbar/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-stafftoolbar/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | *.sqlite3 5 | 6 | # Publishing 7 | /dist 8 | 9 | # Python 10 | /.venv 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # OS files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /forge-stafftoolbar/forgestafftoolbar/__init__.py: -------------------------------------------------------------------------------- 1 | from .links import StaffToolbarLink 2 | 3 | __all__ = ["StaffToolbarLink"] 4 | -------------------------------------------------------------------------------- /forge-stafftoolbar/forgestafftoolbar/links.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse_lazy 2 | 3 | 4 | class StaffToolbarLink: 5 | def __init__(self, *, text, url): 6 | self.text = text 7 | 8 | if not url.startswith("/") and not url.startswith("http"): 9 | self.url = reverse_lazy(url) 10 | else: 11 | self.url = url 12 | -------------------------------------------------------------------------------- /forge-stafftoolbar/forgestafftoolbar/settings.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | from .links import StaffToolbarLink 4 | 5 | 6 | def STAFFTOOLBAR_LINKS(): 7 | return getattr( 8 | settings, 9 | "STAFFTOOLBAR_LINKS", 10 | [ 11 | StaffToolbarLink(text="Admin", url="admin:index"), 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /forge-stafftoolbar/forgestafftoolbar/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-stafftoolbar/forgestafftoolbar/templatetags/__init__.py -------------------------------------------------------------------------------- /forge-stafftoolbar/forgestafftoolbar/templatetags/stafftoolbar.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | from ..core import StaffToolbar 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.inclusion_tag("stafftoolbar/stafftoolbar.html", takes_context=True) 9 | def stafftoolbar(context, outer_class="", inner_class=""): 10 | context["stafftoolbar"] = StaffToolbar(request=context.get("request")) 11 | context["stafftoolbar_outer_class"] = outer_class 12 | context["stafftoolbar_inner_class"] = inner_class 13 | return context 14 | -------------------------------------------------------------------------------- /forge-stafftoolbar/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "forge-stafftoolbar" 3 | packages = [ 4 | { include = "forgestafftoolbar" }, 5 | ] 6 | 7 | version = "0.3.1" 8 | description = "Staff toolbar for Forge" 9 | authors = ["Dave Gaeddert "] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | 14 | [tool.poetry.dev-dependencies] 15 | pytest = "^7.1.2" 16 | ipdb = "^0.13.9" 17 | Django = "^4.0.5" 18 | isort = "^5.10.1" 19 | black = "^22.6.0" 20 | pytest-django = "^4.5.2" 21 | 22 | [build-system] 23 | requires = ["poetry-core>=1.0.0"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /forge-stafftoolbar/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgestafftoolbar "$@" 3 | poetry run isort forgestafftoolbar --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-stafftoolbar/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-stafftoolbar/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-stafftoolbar/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | export DJANGO_SETTINGS_MODULE=tests.settings 3 | poetry run pytest tests "$@" 4 | -------------------------------------------------------------------------------- /forge-stafftoolbar/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-stafftoolbar/tests/templates/index.html: -------------------------------------------------------------------------------- 1 | {% load stafftoolbar %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | {% stafftoolbar %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /forge-stafftoolbar/tests/test_templates.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.contrib.auth.models import User 3 | 4 | 5 | def test_logged_out(client): 6 | response = client.get("/") 7 | assert response.status_code == 200 8 | assert b'id="stafftoolbar"' not in response.content 9 | 10 | 11 | @pytest.mark.django_db 12 | def test_staff_logged_in(client): 13 | User.objects.create_user(username="testuser", password="testuser", is_staff=True) 14 | client.login(username="testuser", password="testuser") 15 | response = client.get("/") 16 | assert response.status_code == 200 17 | assert b'id="stafftoolbar"' in response.content 18 | 19 | 20 | @pytest.mark.django_db 21 | def test_normal_logged_in(client): 22 | User.objects.create_user(username="testuser", password="testuser") 23 | client.login(username="testuser", password="testuser") 24 | response = client.get("/") 25 | assert response.status_code == 200 26 | assert b'id="stafftoolbar"' not in response.content 27 | -------------------------------------------------------------------------------- /forge-stafftoolbar/tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | urlpatterns = [ 5 | path("", TemplateView.as_view(template_name="index.html"), name="index"), 6 | ] 7 | -------------------------------------------------------------------------------- /forge-stripe/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | with: 16 | prepare_cmd: | 17 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 18 | publish_cmd: | 19 | pip3 install -U pip poetry 20 | poetry build 21 | gh release upload $TAG dist/* 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-stripe/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-stripe/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | *.sqlite3 5 | 6 | # Publishing 7 | /dist 8 | 9 | # Python 10 | /.venv 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # OS files 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /forge-stripe/forgestripe/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-stripe/forgestripe/__init__.py -------------------------------------------------------------------------------- /forge-stripe/forgestripe/apps.py: -------------------------------------------------------------------------------- 1 | import stripe 2 | from django.apps import AppConfig 3 | 4 | from . import settings 5 | 6 | 7 | class ForgestripeConfig(AppConfig): 8 | default_auto_field = "django.db.models.BigAutoField" 9 | name = "forgestripe" 10 | 11 | def ready(self): 12 | if settings.STRIPE_SECRET_KEY(): 13 | stripe.api_key = settings.STRIPE_SECRET_KEY() 14 | -------------------------------------------------------------------------------- /forge-stripe/forgestripe/models.py: -------------------------------------------------------------------------------- 1 | import stripe 2 | from django.db import models 3 | from django.utils.functional import cached_property 4 | 5 | 6 | class StripeModel(models.Model): 7 | stripe_id = models.CharField(max_length=255, null=True, blank=True, db_index=True) 8 | 9 | class Meta: 10 | abstract = True 11 | 12 | @cached_property 13 | def stripe_object(self): 14 | """A cached property that is useful when rendering templates""" 15 | return self.get_stripe_object() 16 | 17 | def get_stripe_object(self): 18 | """Automatically get the Stripe object based on the stripe_id prefix""" 19 | if not self.stripe_id: 20 | return None 21 | 22 | if self.stripe_id.startswith("cus_"): 23 | return stripe.Customer.retrieve(self.stripe_id) 24 | 25 | if self.stripe_id.startswith("sub_"): 26 | return stripe.Subscription.retrieve(self.stripe_id) 27 | 28 | raise Exception("Unknown stripe_id prefix") 29 | -------------------------------------------------------------------------------- /forge-stripe/forgestripe/settings.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | from django.conf import settings 4 | 5 | 6 | def STRIPE_SECRET_KEY(): 7 | if "STRIPE_SECRET_KEY" in environ: 8 | return environ["STRIPE_SECRET_KEY"] 9 | else: 10 | return getattr(settings, "STRIPE_SECRET_KEY", None) 11 | 12 | 13 | def STRIPE_WEBHOOK_SECRET(): 14 | if "STRIPE_WEBHOOK_SECRET" in environ: 15 | return environ["STRIPE_WEBHOOK_SECRET"] 16 | else: 17 | return getattr(settings, "STRIPE_WEBHOOK_SECRET", None) 18 | -------------------------------------------------------------------------------- /forge-stripe/forgestripe/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/forge-stripe/forgestripe/templatetags/__init__.py -------------------------------------------------------------------------------- /forge-stripe/forgestripe/templatetags/stripe.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django import template 4 | from django.utils import timezone 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.filter 10 | def epoch_to_datetime(value): 11 | dt = datetime.datetime.utcfromtimestamp(value) 12 | return timezone.make_aware(dt, timezone.get_current_timezone()) 13 | 14 | 15 | @register.filter 16 | def decimal_to_dollars(value, trunc_int=True): 17 | if not value: 18 | return value 19 | 20 | dollars = value / 100.00 21 | 22 | if trunc_int and dollars.is_integer(): 23 | # remove the decimals for friendlier formatting 24 | dollars = int(dollars) 25 | 26 | return dollars 27 | -------------------------------------------------------------------------------- /forge-stripe/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "forge-stripe" 3 | packages = [ 4 | { include = "forgestripe" }, 5 | ] 6 | 7 | version = "0.1.1" 8 | description = "" 9 | authors = ["Dave Gaeddert "] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | stripe = "^5.0.0" 14 | 15 | [tool.poetry.dev-dependencies] 16 | pytest = "^7.1.2" 17 | ipdb = "^0.13.9" 18 | Django = "^4.0.5" 19 | isort = "^5.10.1" 20 | black = "^23.1.0" 21 | pytest-django = "^4.5.2" 22 | 23 | [build-system] 24 | requires = ["poetry-core>=1.0.0"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /forge-stripe/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgestripe "$@" 3 | poetry run isort forgestripe --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-stripe/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-stripe/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-stripe/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | export DJANGO_SETTINGS_MODULE=tests.settings 3 | poetry run pytest tests "$@" 4 | -------------------------------------------------------------------------------- /forge-stripe/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-stripe/tests/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /forge-stripe/tests/test_core.py: -------------------------------------------------------------------------------- 1 | def test_pass(): 2 | pass 3 | -------------------------------------------------------------------------------- /forge-stripe/tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | urlpatterns = [ 5 | path("", TemplateView.as_view(template_name="index.html"), name="index"), 6 | ] 7 | -------------------------------------------------------------------------------- /forge-tailwind/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-tailwind/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-tailwind/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Publishing 6 | /dist 7 | 8 | # Python 9 | /.venv 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # OS files 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /forge-tailwind/forgetailwind/__init__.py: -------------------------------------------------------------------------------- 1 | from .cli import cli 2 | 3 | __all__ = ["cli"] 4 | -------------------------------------------------------------------------------- /forge-tailwind/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgetailwind "$@" 3 | poetry run isort forgetailwind --profile=black "$@" 4 | -------------------------------------------------------------------------------- /forge-tailwind/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-tailwind/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-tailwind/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run pytest tests 3 | poetry run forge tailwind --help 4 | poetry run forge-tailwind --help 5 | -------------------------------------------------------------------------------- /forge-tailwind/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from forgecore import Forge 2 | 3 | 4 | def test_import(): 5 | # Just make sure it loads for now 6 | forge = Forge() 7 | -------------------------------------------------------------------------------- /forge-test/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-test/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-test/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Publishing 6 | /dist 7 | 8 | # Python 9 | /.venv 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # OS files 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /forge-test/README.md: -------------------------------------------------------------------------------- 1 | # forge-test 2 | -------------------------------------------------------------------------------- /forge-test/forgetest/__init__.py: -------------------------------------------------------------------------------- 1 | from .cli import cli 2 | 3 | __all__ = ["cli"] 4 | -------------------------------------------------------------------------------- /forge-test/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgetest "$@" 3 | poetry run black tests "$@" 4 | -------------------------------------------------------------------------------- /forge-test/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-test/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-test/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run pytest tests 3 | poetry run forge test --help 4 | poetry run forge-test --help 5 | -------------------------------------------------------------------------------- /forge-test/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-test/tests/settings.py: -------------------------------------------------------------------------------- 1 | INSTALLED_APPS = [ 2 | "forgetest", 3 | ] 4 | -------------------------------------------------------------------------------- /forge-test/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from forgecore import Forge 2 | 3 | 4 | def test_import(): 5 | # Just make sure it loads for now 6 | forge = Forge() 7 | -------------------------------------------------------------------------------- /forge-work/.github/workflows/nextrelease.yml: -------------------------------------------------------------------------------- 1 | name: nextrelease 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [labeled, unlabeled, edited, synchronize] 8 | 9 | jobs: 10 | sync: 11 | if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && github.head_ref == 'nextrelease' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dropseed/nextrelease@v2 15 | env: 16 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.DROPSEED_PYPI_TOKEN }} 17 | with: 18 | prepare_cmd: | 19 | sed -i -e "s/version = \"[^\"]*\"$/version = \"$VERSION\"/g" pyproject.toml 20 | publish_cmd: | 21 | pip3 install -U pip poetry && poetry publish --build --no-interaction 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /forge-work/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: pipx install poetry 16 | - uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: poetry 20 | - name: Install dependencies 21 | run: poetry install 22 | - name: Test 23 | run: ./scripts/test 24 | -------------------------------------------------------------------------------- /forge-work/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Publishing 6 | /dist 7 | 8 | # Python 9 | /.venv 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # OS files 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /forge-work/forgework/__init__.py: -------------------------------------------------------------------------------- 1 | from .cli import cli 2 | 3 | __all__ = ["cli"] 4 | -------------------------------------------------------------------------------- /forge-work/forgework/debug.py: -------------------------------------------------------------------------------- 1 | import debugpy 2 | 3 | 4 | def attach(endpoint=("localhost", 5678)): 5 | if debugpy.is_client_connected(): 6 | print("Debugger already attached") 7 | return 8 | 9 | debugpy.listen(endpoint) 10 | print("Waiting for debugger to attach...") 11 | debugpy.wait_for_client() 12 | print("Debugger attached!") 13 | -------------------------------------------------------------------------------- /forge-work/scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run black forgework "$@" 3 | poetry run isort forgework --profile=black "$@" 4 | 5 | poetry run black tests "$@" 6 | poetry run isort tests --profile=black "$@" 7 | -------------------------------------------------------------------------------- /forge-work/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | -------------------------------------------------------------------------------- /forge-work/scripts/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | BOLD="\033[1m" 4 | NORMAL="\033[0m" 5 | 6 | echo "${BOLD}Checking formatting${NORMAL}" 7 | ./scripts/format --check 8 | 9 | echo "" 10 | echo "${BOLD}Running tests${NORMAL}" 11 | ./scripts/test 12 | -------------------------------------------------------------------------------- /forge-work/scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | poetry run pytest tests 3 | poetry run forge work --help 4 | poetry run forge-work --help 5 | -------------------------------------------------------------------------------- /forge-work/tests/manage.py: -------------------------------------------------------------------------------- 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", "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() 23 | -------------------------------------------------------------------------------- /forge-work/tests/settings.py: -------------------------------------------------------------------------------- 1 | INSTALLED_APPS = [ 2 | "forgework", 3 | ] 4 | -------------------------------------------------------------------------------- /forge-work/tests/test_core.py: -------------------------------------------------------------------------------- 1 | from forgecore import Forge 2 | 3 | 4 | def test_import(): 5 | # Just make sure it loads for now 6 | forge = Forge() 7 | -------------------------------------------------------------------------------- /pro-billing/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Bundled assets 6 | /app/static/dist 7 | /app/staticfiles 8 | 9 | # Python 10 | /.venv 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # JS 16 | /node_modules 17 | 18 | # OS files 19 | .DS_Store 20 | -------------------------------------------------------------------------------- /pro-billing/README.md: -------------------------------------------------------------------------------- 1 | # Forge Pro Billing 2 | 3 | This is the source code for [billing.forgepackages.com](https://billing.forgepackages.com). 4 | -------------------------------------------------------------------------------- /pro-billing/app/auth.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.contrib.auth.backends import ModelBackend 3 | 4 | UserModel = get_user_model() 5 | 6 | 7 | class EmailModelBackend(ModelBackend): 8 | def authenticate(self, request, username=None, password=None, **kwargs): 9 | if username is None: 10 | email = kwargs.get(UserModel.EMAIL_FIELD) 11 | else: 12 | email = username 13 | 14 | email = UserModel._default_manager.normalize_email(email) 15 | 16 | try: 17 | user = UserModel.objects.get(email=email) 18 | except UserModel.DoesNotExist: 19 | return None 20 | else: 21 | if user.check_password(password) and self.user_can_authenticate(user): 22 | return user 23 | 24 | return None 25 | -------------------------------------------------------------------------------- /pro-billing/app/hosts.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | from django_hosts import host, patterns 4 | 5 | host_patterns = patterns( 6 | "", 7 | host(r"pypi", "pypi_urls", name="packages"), 8 | host(r".*", settings.ROOT_URLCONF, name="billing"), 9 | ) 10 | -------------------------------------------------------------------------------- /pro-billing/app/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.db import models 4 | 5 | 6 | class TimestampModel(models.Model): 7 | created_at = models.DateTimeField(auto_now_add=True) 8 | updated_at = models.DateTimeField(auto_now=True) 9 | 10 | class Meta: 11 | abstract = True 12 | 13 | 14 | class UUIDModel(models.Model): 15 | """An entire abstract model is almost overkill for this, but it's also 16 | very easy to forget to use unique=True, or to set the default incorrectly.""" 17 | 18 | uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) 19 | 20 | class Meta: 21 | abstract = True 22 | -------------------------------------------------------------------------------- /pro-billing/app/packages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/pro-billing/app/packages/__init__.py -------------------------------------------------------------------------------- /pro-billing/app/packages/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Package 4 | 5 | 6 | @admin.register(Package) 7 | class PackageAdmin(admin.ModelAdmin): 8 | list_display = ["name", "repo_url"] 9 | -------------------------------------------------------------------------------- /pro-billing/app/packages/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PackagesConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "packages" 7 | -------------------------------------------------------------------------------- /pro-billing/app/packages/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/pro-billing/app/packages/migrations/__init__.py -------------------------------------------------------------------------------- /pro-billing/app/packages/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from models import TimestampModel, UUIDModel 4 | 5 | 6 | class Package(TimestampModel, UUIDModel): 7 | name = models.SlugField(unique=True) 8 | repo_url = models.URLField(unique=True) 9 | 10 | def __str__(self): 11 | return self.name 12 | 13 | @property 14 | def repo_full_name(self): 15 | return "/".join(self.repo_url.rstrip("/").split("/")[-2:]) 16 | -------------------------------------------------------------------------------- /pro-billing/app/packages/templates/packages/pypi_detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% for asset in assets %} 5 | {{ asset.filename }} 6 | {% endfor %} 7 | 8 | 9 | -------------------------------------------------------------------------------- /pro-billing/app/packages/templates/packages/pypi_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% for package in object_list %} 5 | {{ package.name }} 6 | {% endfor %} 7 | 8 | 9 | -------------------------------------------------------------------------------- /pro-billing/app/packages/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | app_name = "packages" 6 | 7 | urlpatterns = [ 8 | path( 9 | "/", # No slash (IsADirectory error in Poetry) 10 | views.PypiPackageFilenameView.as_view(), 11 | name="pypi_filename", 12 | ), 13 | path("/", views.PypiPackageDetailView.as_view(), name="pypi_detail"), 14 | path("", views.PypiPackageListView.as_view()), 15 | ] 16 | -------------------------------------------------------------------------------- /pro-billing/app/projects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/pro-billing/app/projects/__init__.py -------------------------------------------------------------------------------- /pro-billing/app/projects/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Project 4 | 5 | 6 | @admin.register(Project) 7 | class ProjectAdmin(admin.ModelAdmin): 8 | list_display = ("name", "team") 9 | search_fields = ("name",) 10 | -------------------------------------------------------------------------------- /pro-billing/app/projects/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProjectsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "projects" 7 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0002_project_stripe_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-02-02 20:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("projects", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="project", 15 | name="stripe_id", 16 | field=models.CharField( 17 | blank=True, db_index=True, max_length=255, null=True 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0003_project_github_usernames.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-02-02 22:09 2 | 3 | import django.contrib.postgres.fields 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("projects", "0002_project_stripe_id"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="project", 16 | name="github_usernames", 17 | field=django.contrib.postgres.fields.ArrayField( 18 | base_field=models.CharField(blank=True, max_length=255), 19 | blank=True, 20 | null=True, 21 | size=None, 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0004_alter_project_github_usernames.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-02-02 22:12 2 | 3 | import django.contrib.postgres.fields 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("projects", "0003_project_github_usernames"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="project", 16 | name="github_usernames", 17 | field=django.contrib.postgres.fields.ArrayField( 18 | base_field=models.SlugField(max_length=255), 19 | blank=True, 20 | null=True, 21 | size=None, 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0005_project_status.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-02-02 22:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("projects", "0004_alter_project_github_usernames"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="project", 15 | name="status", 16 | field=models.CharField(default="", max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0006_project_pro_private_key_project_pro_public_key.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.3 on 2022-04-05 21:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("projects", "0005_project_status"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="project", 15 | name="pro_private_key", 16 | field=models.TextField(blank=True), 17 | ), 18 | migrations.AddField( 19 | model_name="project", 20 | name="pro_public_key", 21 | field=models.TextField(blank=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0007_project_packages_token.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.5 on 2022-06-21 19:20 2 | 3 | from django.db import migrations, models 4 | import uuid 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("projects", "0006_project_pro_private_key_project_pro_public_key"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="project", 16 | name="packages_token", 17 | field=models.UUIDField(default=uuid.uuid4, editable=False), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0008_alter_project_name_alter_project_packages_token.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.5 on 2022-06-22 20:52 2 | 3 | from django.db import migrations, models 4 | import uuid 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("projects", "0007_project_packages_token"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="project", 16 | name="name", 17 | field=models.SlugField(max_length=255, unique=True), 18 | ), 19 | migrations.AlterField( 20 | model_name="project", 21 | name="packages_token", 22 | field=models.UUIDField(default=uuid.uuid4), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0009_slugify.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.5 on 2022-06-22 20:53 2 | 3 | from django.db import migrations 4 | from django.utils.text import slugify 5 | 6 | 7 | def forward(apps, schema_editor): 8 | Project = apps.get_model("projects", "Project") 9 | for project in Project.objects.all(): 10 | project.name = slugify(project.name) 11 | project.save() 12 | 13 | 14 | class Migration(migrations.Migration): 15 | 16 | dependencies = [ 17 | ("projects", "0008_alter_project_name_alter_project_packages_token"), 18 | ] 19 | 20 | operations = [ 21 | migrations.RunPython(forward, reverse_code=migrations.RunPython.noop), 22 | ] 23 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0010_alter_project_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.5 on 2022-06-22 20:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("projects", "0009_slugify"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="project", 15 | name="name", 16 | field=models.SlugField( 17 | help_text="Unique slug across all projects", max_length=255, unique=True 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0011_remove_project_pro_private_key_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.5 on 2022-06-23 17:13 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("projects", "0010_alter_project_name"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="project", 15 | name="pro_private_key", 16 | ), 17 | migrations.RemoveField( 18 | model_name="project", 19 | name="pro_public_key", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0012_alter_project_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.5 on 2022-06-23 17:21 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("projects", "0011_remove_project_pro_private_key_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="project", 15 | options={"ordering": ["-name"]}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/0013_alter_project_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.5 on 2023-02-01 04:30 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("projects", "0012_alter_project_options"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="project", 15 | options={"ordering": ["name"]}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /pro-billing/app/projects/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/pro-billing/app/projects/migrations/__init__.py -------------------------------------------------------------------------------- /pro-billing/app/projects/templates/projects/project_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
    6 |
    7 |

    Start a new project

    8 |
    9 |
    10 |
    11 | {% csrf_token %} 12 |
    13 |
    14 | {{ form }} 15 |
    16 |
    17 | 18 | 19 |
    20 |
    21 |
    22 |
    23 |
    24 | 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /pro-billing/app/projects/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /pro-billing/app/projects/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | app_name = "projects" 6 | 7 | urlpatterns = [ 8 | path( 9 | "detail//checkout/", 10 | views.ProjectCheckoutView.as_view(), 11 | name="checkout", 12 | ), 13 | path( 14 | "detail//portal/", 15 | views.ProjectPortalView.as_view(), 16 | name="portal", 17 | ), 18 | path( 19 | "detail//terms/", 20 | views.ProjectTermsView.as_view(), 21 | name="terms", 22 | ), 23 | path( 24 | "detail//token/", 25 | views.ProjectTokenView.as_view(), 26 | name="token", 27 | ), 28 | path("create/", views.ProjectCreateView.as_view(), name="create"), 29 | path("", views.ProjectListView.as_view(), name="list"), 30 | ] 31 | -------------------------------------------------------------------------------- /pro-billing/app/pypi_urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | from django.views.generic import RedirectView 3 | 4 | urlpatterns = [ 5 | path("", include("packages.urls")), 6 | path( 7 | "favicon.ico", 8 | RedirectView.as_view(url="https://www.forgepackages.com/favicon.ico"), 9 | ), 10 | ] 11 | -------------------------------------------------------------------------------- /pro-billing/app/static/forge-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/pro-billing/app/static/forge-logo.png -------------------------------------------------------------------------------- /pro-billing/app/static/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | 3 | 4 | @tailwind components; 5 | 6 | 7 | @tailwind utilities; 8 | -------------------------------------------------------------------------------- /pro-billing/app/teams/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/pro-billing/app/teams/__init__.py -------------------------------------------------------------------------------- /pro-billing/app/teams/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Team, TeamMembership 4 | 5 | 6 | @admin.register(Team) 7 | class TeamAdmin(admin.ModelAdmin): 8 | list_display = ("name", "created_at", "uuid") 9 | search_fields = ("name",) 10 | 11 | 12 | @admin.register(TeamMembership) 13 | class TeamMembershipAdmin(admin.ModelAdmin): 14 | list_display = ("team", "user", "role", "created_at", "uuid") 15 | search_fields = ("team", "user") 16 | list_filter = ("role",) 17 | -------------------------------------------------------------------------------- /pro-billing/app/teams/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TeamsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "teams" 7 | -------------------------------------------------------------------------------- /pro-billing/app/teams/migrations/0002_team_stripe_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-02-02 19:50 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("teams", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="team", 15 | name="stripe_id", 16 | field=models.CharField( 17 | blank=True, db_index=True, max_length=255, null=True 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /pro-billing/app/teams/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/pro-billing/app/teams/migrations/__init__.py -------------------------------------------------------------------------------- /pro-billing/app/templates/400.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Bad request (400){% endblock %} 4 | 5 | {% block content %} 6 |
    7 |

    400

    8 |

    Bad request

    9 |
    10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /pro-billing/app/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Permission denied (403){% endblock %} 4 | 5 | {% block content %} 6 |
    7 |

    403

    8 |

    Permission denied

    9 |
    10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /pro-billing/app/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Page not found (404){% endblock %} 4 | 5 | {% block content %} 6 |
    7 |

    404

    8 |

    Page not found

    9 |
    10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /pro-billing/app/templates/500.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | Server error 9 | 10 | 11 | 12 |
    13 |

    500

    14 |

    Server error

    15 |
    16 | 17 | 18 | -------------------------------------------------------------------------------- /pro-billing/app/templates/registration/password_change_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Password change done{% endblock %} 4 | 5 | {% block content %} 6 |
    7 |
    8 |

    Password change successful

    9 | Back to home 10 |
    11 |
    12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /pro-billing/app/templates/registration/password_change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load widget_tweaks %} 4 | 5 | {% block html_title %}Change your password{% endblock %} 6 | 7 | {% block content %} 8 |
    9 |
    10 |

    Change your password

    11 | 12 |
    13 | {% csrf_token %} 14 | {{ form }} 15 | 16 |
    17 |
    18 |
    19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /pro-billing/app/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Password reset complete{% endblock %} 4 | 5 | {% block content %} 6 |
    7 |
    8 |

    Password reset complete

    9 |

    10 | Your password has been set. You may go ahead and log in now. 11 |

    12 | Log in 13 |
    14 |
    15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /pro-billing/app/templates/registration/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load widget_tweaks %} 4 | 5 | {% block html_title %}Change your password{% endblock %} 6 | 7 | {% block content %} 8 |
    9 | {% if validlink %} 10 | 11 |
    12 |

    Choose a new password

    13 | 14 |
    15 | {% csrf_token %} 16 | {{ form }} 17 | 18 |
    19 |
    20 | 21 | {% else %} 22 | 23 |
    24 |

    Reset link invalid

    25 |

    The password reset link was invalid, possibly because it has already been used. Please request a new password reset.

    26 |
    27 | 28 | {% endif %} 29 |
    30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /pro-billing/app/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Password reset{% endblock %} 4 | 5 | {% block content %} 6 |
    7 |
    8 |

    Password reset sent

    9 |

    10 | We've emailed you instructions for setting your password, if an account exists with the email you entered. 11 | You should receive them shortly. 12 |

    13 |

    14 | If you don't receive an email, please make sure you've entered the address you registered with, 15 | and check your spam folder. 16 |

    17 |
    18 |
    19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /pro-billing/app/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load widget_tweaks %} 4 | 5 | {% block html_title %}Forgot your password?{% endblock %} 6 | 7 | {% block content %} 8 |
    9 |
    10 |

    Forgot your password?

    11 |

    Enter your email address below, and we'll email instructions for setting a new one.

    12 | 13 |
    14 | {% csrf_token %} 15 | {{ form }} 16 | 17 |
    18 |
    19 |
    20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /pro-billing/app/templates/registration/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load widget_tweaks %} 4 | 5 | {% block html_title %}Sign up{% endblock %} 6 | 7 | {% block content %} 8 | 9 |
    10 |
    11 |

    Sign up

    12 | 13 |
    14 | {% csrf_token %} 15 | 16 | {{ form }} 17 | 18 |
    19 | 20 |
    21 | 22 |
    23 |

    Already have an account?

    24 | Log in 25 |
    26 |
    27 |
    28 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /pro-billing/app/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | def test_homepage_exists(client): 2 | response = client.get("/") 3 | assert response.status_code in (200, 301, 302) 4 | -------------------------------------------------------------------------------- /pro-billing/app/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/pro-billing/app/users/__init__.py -------------------------------------------------------------------------------- /pro-billing/app/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | 4 | from .models import User 5 | 6 | 7 | @admin.register(User) 8 | class CustomUserAdmin(UserAdmin): 9 | list_display = ("username", "email", "date_joined") 10 | -------------------------------------------------------------------------------- /pro-billing/app/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "users" 7 | -------------------------------------------------------------------------------- /pro-billing/app/users/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.forms import UserCreationForm 2 | 3 | from .models import User 4 | 5 | 6 | class SignupForm(UserCreationForm): 7 | class Meta: 8 | model = User 9 | fields = ("username", "email", "password1", "password2") 10 | -------------------------------------------------------------------------------- /pro-billing/app/users/migrations/0002_user_uuid_alter_user_email.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-02-02 17:31 2 | 3 | from django.db import migrations, models 4 | import uuid 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("users", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="user", 16 | name="uuid", 17 | field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True), 18 | ), 19 | migrations.AlterField( 20 | model_name="user", 21 | name="email", 22 | field=models.EmailField(max_length=254, unique=True), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /pro-billing/app/users/migrations/0003_user_packages_token.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.5 on 2022-06-22 20:59 2 | 3 | from django.db import migrations, models 4 | import uuid 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("users", "0002_user_uuid_alter_user_email"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="user", 16 | name="packages_token", 17 | field=models.UUIDField(default=uuid.uuid4), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /pro-billing/app/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/pro-billing/app/users/migrations/__init__.py -------------------------------------------------------------------------------- /pro-billing/app/users/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.contrib.auth.models import AbstractUser 4 | from django.db import models 5 | 6 | from models import UUIDModel 7 | 8 | 9 | class User(AbstractUser, UUIDModel): 10 | email = models.EmailField(unique=True) 11 | 12 | packages_token = models.UUIDField(default=uuid.uuid4) 13 | -------------------------------------------------------------------------------- /pro-billing/app/users/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import redirect 2 | from django.urls import reverse_lazy 3 | from django.views import generic 4 | 5 | from teams.models import Team, TeamMembership 6 | 7 | from .forms import SignupForm 8 | 9 | 10 | class SignupView(generic.CreateView): 11 | form_class = SignupForm 12 | success_url = reverse_lazy("login") 13 | template_name = "registration/signup.html" 14 | 15 | def dispatch(self, request, *args, **kwargs): 16 | if request.user.is_authenticated: 17 | return redirect("/") 18 | return super().dispatch(request, *args, **kwargs) 19 | 20 | def form_valid(self, form): 21 | user = form.save() 22 | 23 | team = Team.objects.create(name=user.username) 24 | TeamMembership.objects.create(user=user, team=team) 25 | 26 | return super().form_valid(form) 27 | -------------------------------------------------------------------------------- /pro-billing/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "project" 3 | version = "0.0.0" 4 | description = "" 5 | authors = [] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.9" 9 | 10 | PyJWT = "^2.3.0" 11 | cryptography = "^37.0.2" 12 | django-hosts = "^5.1" 13 | django = "^4.1.5" 14 | django-widget-tweaks = "^1.4.12" 15 | dj-database-url = "^1.2.0" 16 | whitenoise = "^6.3.0" 17 | redis = "^4.4.2" 18 | hiredis = "^2.1.1" 19 | ipython = "^8.9.0" 20 | python-dotenv = "^0.21.1" 21 | 22 | forge-pro = "<1.0.0" 23 | forge-logging = "^1.0.0" 24 | forge-heroku = "^1.0.0" 25 | forge-tailwind = "^1.0.0" 26 | 27 | [tool.poetry.group.dev.dependencies] 28 | forge-work = "^1.0.0" 29 | forge-test = "^1.0.1" 30 | forge-format = "^1.0.0" 31 | forge-precommit = "^1.2.0" 32 | forge-db = "^1.0.0" 33 | 34 | [tool.ruff] 35 | # Never enforce `E501` (line length violations). 36 | ignore = ["E501"] 37 | 38 | [build-system] 39 | requires = ["poetry-core>=1.0.0"] 40 | build-backend = "poetry.core.masonry.api" 41 | 42 | [[tool.poetry.source]] 43 | name = "forgepackages" 44 | url = "https://pypi.forgepackages.com/" 45 | -------------------------------------------------------------------------------- /pro-billing/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 3 | 4 | poetry run forge pre-commit --install 5 | -------------------------------------------------------------------------------- /pro-billing/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const FORGE_TAILWIND_VERSION = "3.0.24" 2 | 3 | module.exports = { 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [ 8 | require("@tailwindcss/forms"), 9 | ], 10 | } 11 | -------------------------------------------------------------------------------- /scripts/import: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ORG="forgepackages" 4 | BRANCH="master" 5 | 6 | # Make a temp directory 7 | mkdir -p tmp 8 | cd tmp || exit 1 9 | 10 | # Get all repos for the org 11 | repos=$(gh repo list $ORG --json name -q '.[].name') 12 | 13 | cd .. 14 | 15 | for repo in $repos; do 16 | echo "Processing $repo..." 17 | # Clone into a temporary spot 18 | gh repo clone "$ORG/$repo" "tmp/$repo" 19 | 20 | # Add as a subtree in the current repo 21 | git subtree add --prefix="$repo" "tmp/$repo" "$BRANCH" 22 | 23 | # Clean up 24 | rm -rf "tmp/$repo" 25 | done 26 | 27 | # Remove the tmp directory if empty 28 | rmdir tmp 2>/dev/null 29 | -------------------------------------------------------------------------------- /scripts/publish: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | for package_dir in forge-*; do 4 | cd $package_dir 5 | rm -rf dist 6 | poetry build 7 | poetry publish --no-interaction --skip-existing 8 | cd .. 9 | done 10 | -------------------------------------------------------------------------------- /scripts/quickstart-test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # Set up a forge-quickstart-test directory next to this repo 3 | cd .. 4 | rm -rf forge-quickstart-test 5 | cat forge/quickstart.py | python3 - forge-quickstart-test --source ./starter-template 6 | -------------------------------------------------------------------------------- /starter-template/.env.example: -------------------------------------------------------------------------------- 1 | DEBUG=true 2 | SECRET_KEY=test 3 | DATABASE_URL=postgres://postgres:postgres@localhost:54322/postgres 4 | BASE_URL=http://127.0.0.1:8000 5 | APP_LOG_LEVEL=DEBUG 6 | -------------------------------------------------------------------------------- /starter-template/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | services: 9 | postgres: 10 | image: postgres:13 11 | ports: ["5432:5432/tcp"] 12 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 13 | env: 14 | POSTGRES_USER: postgres 15 | POSTGRES_PASSWORD: postgres 16 | POSTGRES_DB: postgres 17 | 18 | steps: 19 | - run: sudo apt update && sudo apt install libpq-dev 20 | - uses: actions/checkout@v3 21 | - run: pipx install poetry 22 | - uses: actions/setup-python@v4 23 | with: 24 | python-version: "3.9" 25 | cache: poetry 26 | - run: poetry install 27 | - run: poetry run forge test 28 | env: 29 | DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres 30 | DEBUG: true 31 | SECRET_KEY: testing 32 | BASE_URL: http://example.com 33 | -------------------------------------------------------------------------------- /starter-template/.gitignore: -------------------------------------------------------------------------------- 1 | # Local development files 2 | /.env 3 | /.forge 4 | 5 | # Bundled assets 6 | /app/static/dist 7 | 8 | # Collected staticfiles 9 | /app/staticfiles 10 | 11 | # Python 12 | /.venv 13 | __pycache__/ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # JS 18 | /node_modules 19 | 20 | # OS files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /starter-template/app/auth.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.contrib.auth.backends import ModelBackend 3 | 4 | UserModel = get_user_model() 5 | 6 | 7 | class EmailModelBackend(ModelBackend): 8 | def authenticate(self, request, username=None, password=None, **kwargs): 9 | if username is None: 10 | email = kwargs.get(UserModel.EMAIL_FIELD) 11 | else: 12 | email = username 13 | 14 | email = UserModel._default_manager.normalize_email(email) 15 | 16 | try: 17 | user = UserModel.objects.get(email=email) 18 | except UserModel.DoesNotExist: 19 | return None 20 | else: 21 | if user.check_password(password) and self.user_can_authenticate(user): 22 | return user 23 | 24 | return None 25 | -------------------------------------------------------------------------------- /starter-template/app/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.forms import UserCreationForm 2 | from users.models import User 3 | 4 | 5 | class SignupForm(UserCreationForm): 6 | class Meta: 7 | model = User 8 | fields = ("username", "email", "password1", "password2") 9 | -------------------------------------------------------------------------------- /starter-template/app/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.db import models 4 | 5 | 6 | class TimestampModel(models.Model): 7 | created_at = models.DateTimeField(auto_now_add=True) 8 | updated_at = models.DateTimeField(auto_now=True) 9 | 10 | class Meta: 11 | abstract = True 12 | 13 | 14 | class UUIDModel(models.Model): 15 | """An entire abstract model is almost overkill for this, but it's also 16 | very easy to forget to use unique=True, or to set the default incorrectly.""" 17 | 18 | uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) 19 | 20 | class Meta: 21 | abstract = True 22 | -------------------------------------------------------------------------------- /starter-template/app/static/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | 3 | 4 | @tailwind components; 5 | 6 | 7 | @tailwind utilities; 8 | -------------------------------------------------------------------------------- /starter-template/app/teams/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/starter-template/app/teams/__init__.py -------------------------------------------------------------------------------- /starter-template/app/teams/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Team, TeamMembership 4 | 5 | 6 | @admin.register(Team) 7 | class TeamAdmin(admin.ModelAdmin): 8 | list_display = ("name", "created_at", "uuid") 9 | search_fields = ("name",) 10 | 11 | 12 | @admin.register(TeamMembership) 13 | class TeamMembershipAdmin(admin.ModelAdmin): 14 | list_display = ("team", "user", "role", "created_at", "uuid") 15 | search_fields = ("team", "user") 16 | list_filter = ("role",) 17 | -------------------------------------------------------------------------------- /starter-template/app/teams/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TeamsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "teams" 7 | -------------------------------------------------------------------------------- /starter-template/app/teams/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/starter-template/app/teams/migrations/__init__.py -------------------------------------------------------------------------------- /starter-template/app/teams/urls.py: -------------------------------------------------------------------------------- 1 | app_name = "teams" 2 | 3 | urlpatterns = [] 4 | -------------------------------------------------------------------------------- /starter-template/app/templates/400.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Bad request (400){% endblock %} 4 | 5 | {% block content %} 6 |
    7 |

    400

    8 |

    Bad request

    9 |
    10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /starter-template/app/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Permission denied (403){% endblock %} 4 | 5 | {% block content %} 6 |
    7 |

    403

    8 |

    Permission denied

    9 |
    10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /starter-template/app/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Page not found (404){% endblock %} 4 | 5 | {% block content %} 6 |
    7 |

    404

    8 |

    Page not found

    9 |
    10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /starter-template/app/templates/500.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | Server error 9 | 10 | 11 | 12 |
    13 |

    500

    14 |

    Server error

    15 |
    16 | 17 | 18 | -------------------------------------------------------------------------------- /starter-template/app/templates/django/forms/errors/list/default.html: -------------------------------------------------------------------------------- 1 | {% if errors|length == 1 %} 2 | {{ errors.0 }} 3 | {% elif errors|length > 1 %} 4 |
      5 | {% for error in errors %}
    • {{ error }}
    • {% endfor %} 6 |
    7 | {% endif %} 8 | -------------------------------------------------------------------------------- /starter-template/app/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

    Hello, {{ request.user }}!

    5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /starter-template/app/templates/registration/password_change_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Password change done{% endblock %} 4 | 5 | {% block content %} 6 |
    7 |
    8 |

    Password change successful

    9 | Back to home 10 |
    11 |
    12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /starter-template/app/templates/registration/password_change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load widget_tweaks %} 4 | 5 | {% block html_title %}Change your password{% endblock %} 6 | 7 | {% block content %} 8 |
    9 |
    10 |

    Change your password

    11 | 12 |
    13 | {% csrf_token %} 14 | {{ form }} 15 | 16 |
    17 |
    18 |
    19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /starter-template/app/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Password reset complete{% endblock %} 4 | 5 | {% block content %} 6 |
    7 |
    8 |

    Password reset complete

    9 |

    10 | Your password has been set. You may go ahead and log in now. 11 |

    12 | Log in 13 |
    14 |
    15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /starter-template/app/templates/registration/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load widget_tweaks %} 4 | 5 | {% block html_title %}Change your password{% endblock %} 6 | 7 | {% block content %} 8 |
    9 | {% if validlink %} 10 | 11 |
    12 |

    Choose a new password

    13 | 14 |
    15 | {% csrf_token %} 16 | {{ form }} 17 | 18 |
    19 |
    20 | 21 | {% else %} 22 | 23 |
    24 |

    Reset link invalid

    25 |

    The password reset link was invalid, possibly because it has already been used. Please request a new password reset.

    26 |
    27 | 28 | {% endif %} 29 |
    30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /starter-template/app/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block html_title %}Password reset{% endblock %} 4 | 5 | {% block content %} 6 |
    7 |
    8 |

    Password reset sent

    9 |

    10 | We've emailed you instructions for setting your password, if an account exists with the email you entered. 11 | You should receive them shortly. 12 |

    13 |

    14 | If you don't receive an email, please make sure you've entered the address you registered with, 15 | and check your spam folder. 16 |

    17 |
    18 |
    19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /starter-template/app/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load widget_tweaks %} 4 | 5 | {% block html_title %}Forgot your password?{% endblock %} 6 | 7 | {% block content %} 8 |
    9 |
    10 |

    Forgot your password?

    11 |

    Enter your email address below, and we'll email instructions for setting a new one.

    12 | 13 |
    14 | {% csrf_token %} 15 | {{ form }} 16 | 17 |
    18 |
    19 |
    20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /starter-template/app/templates/registration/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load widget_tweaks %} 4 | 5 | {% block html_title %}Sign up{% endblock %} 6 | 7 | {% block content %} 8 | 9 |
    10 |
    11 |

    Sign up

    12 | 13 |
    14 | {% csrf_token %} 15 | 16 | {{ form }} 17 | 18 |
    19 | 20 |
    21 | 22 |
    23 |

    Already have an account?

    24 | Log in 25 |
    26 |
    27 |
    28 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /starter-template/app/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | def test_homepage_exists(client): 2 | response = client.get("/") 3 | assert response.status_code in (200, 301, 302) 4 | -------------------------------------------------------------------------------- /starter-template/app/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/starter-template/app/users/__init__.py -------------------------------------------------------------------------------- /starter-template/app/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | 4 | from .models import User 5 | 6 | 7 | @admin.register(User) 8 | class CustomUserAdmin(UserAdmin): 9 | list_display = ("username", "email", "date_joined") 10 | -------------------------------------------------------------------------------- /starter-template/app/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "users" 7 | -------------------------------------------------------------------------------- /starter-template/app/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropseed/forge/d593b3a57234d0a411f4721391895d241b2caf98/starter-template/app/users/migrations/__init__.py -------------------------------------------------------------------------------- /starter-template/app/users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | from models import UUIDModel 4 | 5 | 6 | class User(AbstractUser, UUIDModel): 7 | email = models.EmailField(unique=True) 8 | -------------------------------------------------------------------------------- /starter-template/app/users/templates/users/user_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
    6 | {% csrf_token %} 7 | {{ form }} 8 | 9 |
    10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /starter-template/app/users/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import UpdateView 2 | from views import BaseLoggedInMixin 3 | 4 | from .models import User 5 | 6 | 7 | class MyAccountView(BaseLoggedInMixin, UpdateView): 8 | html_title = "My account" 9 | model = User 10 | fields = ("username", "email", "first_name", "last_name") 11 | success_url = "." 12 | 13 | def get_object(self, queryset=None): 14 | return self.request.user 15 | -------------------------------------------------------------------------------- /starter-template/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "project" 3 | version = "0.0.0" 4 | description = "" 5 | authors = [] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.9" 9 | 10 | Django = "^4.1.1" 11 | django-widget-tweaks = "^1.4.12" 12 | dj-database-url = "^1.0.0" 13 | whitenoise = "^6.0.0" 14 | python-dotenv = "*" 15 | redis = "^4.2.2" 16 | hiredis = "^2.0.0" 17 | ipython = "^8.5.0" 18 | 19 | forge-logging = "^1.0.0" 20 | forge-heroku = "^1.0.0" 21 | forge-tailwind = "^1.0.0" 22 | 23 | [tool.poetry.group.dev.dependencies] 24 | forge-work = "^1.0.0" 25 | forge-test = "^1.0.0" 26 | forge-format = "^1.0.0" 27 | forge-precommit = "^1.0.0" 28 | forge-db = "^1.0.0" 29 | 30 | [tool.ruff] 31 | ignore = [ 32 | "E501", # Never enforce `E501` (line length violations). 33 | ] 34 | extend-select = [ 35 | "I", # Enable isort 36 | ] 37 | 38 | [build-system] 39 | requires = ["poetry-core>=1.0.0"] 40 | build-backend = "poetry.core.masonry.api" 41 | -------------------------------------------------------------------------------- /starter-template/scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | BOLD=$(tput bold) 3 | NORMAL=$(tput sgr0) 4 | 5 | echo "${BOLD}Installing dependencies with Poetry${NORMAL}" 6 | POETRY_VIRTUALENVS_IN_PROJECT=true poetry install 7 | 8 | if [ ! -f .env ]; then 9 | echo "" 10 | echo "${BOLD}Creating development .env from .env.example${NORMAL}" 11 | cp .env.example .env 12 | cat .env 13 | fi 14 | 15 | echo "" 16 | echo "${BOLD}Installing git pre-commit hook${NORMAL}" 17 | poetry run forge pre-commit --install 18 | -------------------------------------------------------------------------------- /starter-template/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: { 3 | extend: { 4 | // Add custom colors etc here 5 | }, 6 | }, 7 | plugins: [ 8 | require("@tailwindcss/forms"), 9 | ], 10 | } 11 | --------------------------------------------------------------------------------