Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
October 4, 2022 09:08 am GMT

Writing Jinja as a proper Python developer

disclaimer

I have no idea what is the correct way to do things. I probably do some things really wrong, and I will be glad to learn that. Here I'm just sharing my experience and where I'm now in using Flask for multiple small-to-medium projects (all basically CRUD apps). Your experience may be different, and I will gladly hear it.

writing Jinja as a proper Python developer

You probably heard about DRY principle. I'm not going into heated discussion if that's something you should code by. Actually, let's forget there's DRY principle at all.

Lets just say that I'm lazy person.

So, idea of writing code I don't have to multiple times is, well, not appealing to me.

But it happens. Especially when project is young, dynamic and you have no idea what code you will need, or how you will structure things.
One place I extremely hate writing code over and over again is HTML. There are so many <s and >s and other things. And soo much thats obvious and shouldn't be written.
you know - if you are writing <a> tag, of course you will use href argument. Why is it even kwarg? I would never be kwarg if I was writing it in Python!
And as I'm programming Flask CRUD app, most of the time <a> is pointing to some object show/edit page. Should I write
<a href="{{ url_for('UserView:show', id=user.id) }}> {{ user.name }} </a>" every time? Absolutely not!

As I'm exposed to Ruby on Rails in my work, I've seen link_to in templates a lot. And I thought: that's really nice! That's what I should be doing, right? Just writing {{ link_to(user) }} in my template, when i want really simple link. Why can't I do that?

Lt's look into what did I discover on my journey into great developer experience with Jinja templates.

The question was - how can I create some "helpers" like this in Jinja?
And the answer is...

There are many ways depending on context. Sigh.

Well, lets look into them.

Show me what you got!

partials

By partial I mean separate template that I include in my "main" template. They are useful when there's some big part of page (like table of all users) that I use on multiple pages (index, show).
I can give them some variables using {% with users=... %}, but if I try adding some complex logic, it's not really great.

macros

When you were reading this article, you probably though "he's talking about macros, right?". Well, of course, macros are THE suggested way to handle bit more generalized, repeated pieces of HTML.
It's mosly only advice you get on StackOverflow (here and here just two examples). And macros are great! I used them happily for some time - having macro for rendering form fields, icons and other repeated elements. I had macros that use other macros! But...

Jinja logic is somewhat.. ..more verbose than I'm used to from Python. Not a great code. And getting some more complex logic in, it's not really what you want.
Another thing with macros is that you have to import them into your templates. Normally it's just a matter of adding everything to base.html.j2 template, but as I use Turbo (another article on that), sometimes I render just a partial, so I need to import explicitely there too. I know that Zen of Python says "Explicit is better than implicit.", but in this case, I don't know. Having link_to acessible globally seems like a sensible thing to me (and another zen says "Although practicality beats purity.").

So, how do we do that?

We will have to come back for dark, messy realms of HTML to sweet embrace of Python.

helper functions

I did know that I can register custom Jinja filters and test (you know, because I read Jinja API documentation more times than I would like to).
But for a long time one great method was hiding from me: application.add_template_global().
What is it? Simple, just a method to register custom functions to use in Jinja. They are used in code like macro, but they are your normal Python functions.

Nice, let's use that!

I created new directory: app/components and there created this file:

# app/components/links.pyfrom flask import Markupdef link_to(obj):    # (do some logic)    return Markup(some_html_code)

and then, to make things nice, I added:

# app/components/__init__.pyfrom .links import link_todef register_all_components(application):    application.add_template_global(link_to)

and finally, in my app/__init__.py (simplified):

# app/__init__.pydef create_app():    application = Flask()    (...)    from app.components import register_all_components    register_all_components(application)    (...)

Now I can use {{ link_to(something) }} enywhere in my templates. Smooth!

You can probably guess what some logic in link_to does, and I don't want to include full code here as it's but sprawled, but basic functionality goes like this:

  • i can pass either object or string to link_to
  • if it's string
    • it either includes :, so it's recognized as Flask-Classfull reference to route (eg. "UserView:index"), so it's converted to url_for(something)
    • or it's just pure url, then i just pass it as it is
  • or it's object
    • then I get generated route - so, from user object I can expect UserView, add :show and id=user.id.This logic is handled by UserPresenter which gives us user.path_to_show

I was happy with this. But I wasn't happy for long. Because soon I got repeating code again, and it was just not beautiful enough. So I my journey to perfect developer experience continued and I found...

kittens!

Kitten

No, sadly kittens are of no direct help with HTML. They are way too cute for that.

We need to continue our search..

TemplateComponents

You never heard of TemplateComponents? That's understandable, they don't exist.
Inspiration for this pattern comes from Ruby on Rails, and specifically gem(package) created and used by Github - ViewComponent.
As views in Rails and Django are templates in Flask (I don't know why, I would be happy with MVC instead of MTV), let's call it TemplateComponents!

The idea is to move the core of these reusable pieces of HTML into Python objects, getting all the benefits of OOP.

How would that look?

Here's an example:

1 Components are classes, in my case located in app/components/.

2 Component itself does some logic and then renders jinja template (with minimum logic from app/templates/components/

3 They are used by helper functions (defined in same place), these helpers are registered by flask to be globally accessible, eg.(application.add_template_global(icon))

Here's example how it can be set up:

# app/__init__.pydef create_app():    (...)    from app.components import register_all_components    register_all_components(application)    (...)

Nothing new here..

# app/components/__init__.pyfrom .links import link_to, link_to_editdef register_all_components(application):    application.add_template_global(link_to)    application.add_template_global(link_to_edit)

Yeah, we know this part.

# app/components/links.pyfrom app.components import Component# coreclass Link(Component):    def __init__(self, path, value, **kwargs):        super(Link, self).__init__(**kwargs)        self.path = path        self.value = value# helpersdef link_to(obj, **kwargs):    path = obj.path_to_show()    value = kwargs.pop("value", obj.default_value_link)    return Link(path=path, value=value, **kwargs).render()def link_to_edit(obj, **kwargs):    from app.components import icon    path = obj.path_to_edit()    value = kwargs.pop("value", icon("edit"))    return Link(path=path, value=value, **kwargs).render()

Ok, here's something new. But Link itself doesn't have a lot of logic, so how..

# app/components/base.pyfrom flask import render_template as renderfrom flask import Markupclass Component:    def __init__(self, **kwargs):        self.kwargs = kwargs        self.kwargs["data"] = self.kwargs.pop("data", {})    def render(self):        return Markup(render(self.template, **self.attributes))    @property    def template(self):        folder = getattr(self, "folder", f"{self.__class__.__name__.lower()}s")        file = getattr(self, "file", f"{self.__class__.__name__.lower()}")        return f"components/{folder}/{file}.html.j2"    @property    def attributes(self):        # All public variables of the view are passed to template        class_attributes = self.__class__.__dict__        view_attributes = self.__dict__        all_attributes = class_attributes | view_attributes        public_attributes = {            k: all_attributes[k] for k in all_attributes if not k.startswith("_")        }        # kwargs has higher priority, therefore rewrites public attributes        merged_values = {**public_attributes, **self.kwargs}        return merged_values

Oh, right! Because there's some logic that will probably be used in any component, so lets move that to Component class!

And here's a template it renders:

# app/templates/components/links/link.html.j2<a    href="{{ path }}"    {% if class %} class="{{ class }}" {% endif %}>{{ value }}</a>

Mind you, this is first version of my Component system. I haven't even moved all my macros there, so there will be probably some changes and improvements. But the basic idea is here.

For now, I'm not pushing component classes to jinja context - so I cannot do {{ Link(something) }}.
It would be possible, but for now it makes sense to me to use them through helper function such as link_to and link_to_edit.

Why am I happy (for now) with this approach?

  • I can handle complex logic easily (as I'm using Python, not Jinja)
  • there's clear way to split logic for
    • components in general (handled by Component class) - getting template name, rendering it
    • kinds of components (handled by Link class for example) - possibly setting some defaults - style,...
    • and specific helpers (link_to, link_to_edit) - setting specific values - path and defaults.
  • it's easily testable!
    • i can test class or helper. not doing it now as I only have simple usecases, but in future it will make sense - so I can check parts of my HTML in fast unit test instead of complicated slow FE test.

As always, I'm not saying this is the best approach. I was trying to show all the styles I used for this part of creating web app. All may be useful, it depends on what you need.

Question for this post:

  • how do you handle your templates?
  • how important is "Explicit is better than implicit" zen important to you?

Hope to hear your ideas, experiences and critique!


Original Link: https://dev.to/janmpeterka/writing-jinja-as-a-proper-python-developer-1c4i

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To