reactpy-django icon indicating copy to clipboard operation
reactpy-django copied to clipboard

Add Jinja support

Open ShaheedHaque opened this issue 2 years ago • 7 comments
trafficstars

Description

WORK IN PROGRESS: DO NOT MERGE.

~~This is incomplete support for Jinja2-based templates. I could use some pointers to finish the missing part (see comments at the end of src/reactpy_django/templatetags/jinja.py).~~

(also missing, docs, packaging and tests)

To reproduce the results to date requires the following example changes on the Django side...

  1. Configure template files ending in ".jinja" to be processed via Jinja:

        TEMPLATES = [
        {
            "BACKEND": "django_jinja.backend.Jinja2",  # Jinja backend
            'DIRS': [os.path.join(BASE_DIR, 'templates')],
            "APP_DIRS": True,
            "OPTIONS": {
                'context_processors': [
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                    ...
                ],
                "match_extension": (".jinja"),  # suffix for Jinja templates
                "app_dirname": "templates",
    
                # Can be set to "jinja2.Undefined" or any other subclass.
                "undefined": None,
    
                "newstyle_gettext": True,
                "tests": {
                    # "mytest": "path.to.my.test",
                },
                "filters": {
                    # "myfilter": "path.to.my.filter",
                },
                "globals": {
                    # "myglobal": "path.to.my.globalfunc",
                },
                "constants": {
                    # "foo": "bar",
                },
                "extensions": [
                    "jinja2.ext.do",
                    "jinja2.ext.loopcontrols",
                    "jinja2.ext.i18n",
                    "django_jinja.builtins.extensions.CsrfExtension",
                    "django_jinja.builtins.extensions.CacheExtension",
                    "django_jinja.builtins.extensions.TimezoneExtension",
                    "django_jinja.builtins.extensions.UrlsExtension",
                    "django_jinja.builtins.extensions.StaticFilesExtension",
                    "django_jinja.builtins.extensions.DjangoFiltersExtension",
                    "reactpy_django.templatetags.jinja.ReactPyExtension",    # new extension
                ],
                "bytecode_cache": {
                    "name": "default",
                    "backend": "django_jinja.cache.BytecodeCache",
                    "enabled": False,
                },
                "autoescape": True,
                "auto_reload": DEBUG,
                "translation_engine": "django.utils.translation",
            },
        },
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates', # Django templates.
    
    
  2. Add the new app:

    INSTALLED_APPS = [
         ...
        "reactpy_django",
     ]
    
    
  3. File of components in components.py:

    from reactpy import component, html
    
    @component
    def hello_world(recipient: str):
        return html.h1(f"Hello {recipient}!")
    
  4. Test view in views/react.py:

    from django.shortcuts import render
    
    def jinja_view(request):
        return render(request, "my-template.html.jinja")
    
  5. Template for the view templates/my-template.html.jinja:

    <!DOCTYPE html>
    <html>
    <body>
    {{ component("paiyroll.components.hello_world", recipient="World") }}
    </body>
    </html>
    
  6. project/asgi.py

    from reactpy_django import REACTPY_WEBSOCKET_ROUTE
    
    application = ProtocolTypeRouter({
      'http': get_asgi_application(),
      'websocket': AuthMiddlewareStack(
          URLRouter(
            ... + [REACTPY_WEBSOCKET_ROUTE]
         )
    
  7. And finally project/urls.py:

    from ...views import react
    
    urlpatterns = [
         ...
         path("reactpy/", include("reactpy_django.http.urls")),
         path("react/", react.jinja_view),
     ]
    

Once Django is restarted, navigating to the view, you should see Hello World!

Checklist:

Please update this checklist as you complete each item:

  • [ ] Tests have been developed for bug fixes or new functionality.
  • [ ] The changelog has been updated, if necessary.
  • [ ] Documentation has been updated, if necessary.
  • [ ] GitHub Issues closed by this PR have been linked.

By submitting this pull request you agree that all contributions comply with this project's open source license(s).

ShaheedHaque avatar Oct 09 '23 20:10 ShaheedHaque

OK, this now render the "Hello World!" correctly.

Feedback requested before thinking about tests/docs etc.

ShaheedHaque avatar Oct 10 '23 00:10 ShaheedHaque

Resolved last outstanding comment. And ran black.

If we are to proceed, I'd propose to squash the current commits before thinking about tests/packaging/docs etc. (FWIW, in that regard, I assume the appropriate packaging would be as something like "reactpy_django[jinja]"...but I'm not currently sure how to go about implementing that).

ShaheedHaque avatar Nov 26 '23 20:11 ShaheedHaque

Optional dependencies can be defined within the package in setup.py. For example:

{
  "extras_require": {
    "encryption": ["cryptography", "pycryptodome"],
  },
  ...
}

Archmonger avatar Nov 27 '23 09:11 Archmonger

Optional dependencies can be defined within the package in setup.py. For example:

{
  "extras_require": {
    "encryption": ["cryptography", "pycryptodome"],
  },
  ...
}

Thanks, I'm more or less familiar with this bit. I was more thinking about the new file I have added (and any supporting test file), and whether that could/should be omitted unless "[jinja] was specified?

ShaheedHaque avatar Nov 27 '23 14:11 ShaheedHaque

I have some changes in one of my PRs that will simplify test configuration https://github.com/reactive-python/reactpy-django/pull/190

TLDR: Our test suite can now run multiple different Django settings.py files.

And yes, from the user's perspective we should have all the jinja dependencies be optional via reactpy_django[jinja]

Archmonger avatar Dec 31 '23 20:12 Archmonger

My PR has been merged. All settings_*.py files within the test app will now automatically run tests against them.

There shouldn't be anything blocking this PR anymore, so you'll need to do the following:

  1. Add the needed dependencies to the test-env.txt
  2. Add the needed dependencies to the optional extras using the [jinja] keyword.
  3. Copy settings_single_db.py to create settings_jinja.py.
  4. Write some conditional tests within the test folder.
    • We don't want to run jinja tests in other testing environments, so we'll need a flag within settings.py to conditionally run these tests.
    • This might extend to preventing all other tests to run within the Jinja environment. In a perfect world, we would have some way of magically re-using all our old tests within a Jinja test environment, not sure if that's feasible though. I'll let you figure that best solution to that.
  5. Write a new page of documentation based on the current installation page.
    • Probably will require changing the mkdocs.yml nav menu to look something like
    • Get Started > Add ReactPy to your ... > Django Project
    • Get Started > Add ReactPy to your ... > Django-Jinja Project

Archmonger avatar Jan 08 '24 06:01 Archmonger

Noted. I've a full plate right now but have this on my radar.

ShaheedHaque avatar Jan 11 '24 02:01 ShaheedHaque