Why not just use Python for templating smart components?

72 views
Skip to first unread message

Bobby Mozumder

unread,
Jul 30, 2019, 12:31:06 PM7/30/19
to Django developers (Contributions to Django itself)
I’ve been testing out some custom Python classes for Django templating, due to Python 3.6’s addition of F-strings. In addition, templates now have the full capability to be programmed in Python itself, with the possibility of creating smart templates and components based on Python classes.

Instead of the template language (copying and pasting just a typical Django template here):

{% extends "base.html" %}

{% block content %}


      <!--Section: Products v.3-->
      <section class="text-center mb-4">

        <!--Grid row-->
        <div class="row wow fadeIn">
{% for item in items %}

          <!--Grid column-->
          <div class="col-lg-3 col-md-6 mb-4">

            <!--Card-->
            <div class="card">

              <!--Card image-->
              <div class="view overlay">
                <img src="https://mdbootstrap.com/img/Photos/Horizontal/E-commerce/Vertical/12.jpg" class="card-img-top"
                  alt="">
                <a>
                  <div class="mask rgba-white-slight"></div>
                </a>
              </div>
              <!--Card image-->

              <!--Card content-->
              <div class="card-body text-center">
                <!--Category & Title-->
                <a href="" class="grey-text">
                  <h5>Shirt</h5>
                </a>
                <h5>
                  <strong>
                    <a href="" class="dark-grey-text">{{item}}
                      <span class="badge badge-pill danger-color">NEW</span>
                    </a>
                  </strong>
                </h5>

                <h4 class="font-weight-bold blue-text">
                  <strong>120$</strong>
                </h4>

              </div>
              <!--Card content-->

            </div>
            <!--Card-->

          </div>
          <!--Grid column-->
{% endfor %}

        </div>
        <!--Grid row-->

      </section>
      <!--Section: Products v.3-->

{% endblock content %}

I have something like (example from a different project):


base_css = """
body {
    margin: 2% auto 5% auto;
    background: #fdfdf2;
    color: #111;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    font-size: 16px;
    line-height: 1.8;
    text-shadow: 0 1px 0 #ffffff;
    max-width: 73%;
    }
h1, h2, h3, h4 {
    line-height:1.2
    }
h2, h3 {
    color: #777
    }
code {
    background: white;
    }
a {
    border-bottom: 1px solid #444444;
    color: #444444;
    text-decoration: none;
    }
a:hover {
    border-bottom: 0;
    }
.fixed {
    white-space: nowrap;
    vertical-align:top;
    }
"""

class viewIndexHead(Block):

    css = base_css

    vary = []

    def generateHTML(self,page):
        return f'<title>Bobby Mozumder Homepage</title></head><body><h4><a href="/">mozumder.net</a></h4>'

class viewArticleHead(Block):

    css = base_css

    vary = ['id']

    def generateHTML(self,page):
        return f'<title>{page.data.title}</title></head><body><h4><a href="/">mozumder.net</a></h4>'


class article_body(Block):
    vary = ['id']

    def generateHTML(self,page):
        html = f'<h1>{page.data.headline}</h1><h2>{page.data.subheadline}</h2><h3>{page.data.date_published:%B %d, %Y}</h3>{page.data.body}'
        return html

class article_list(Block):
    sql = 'get_view_headlines_fragment'
    components = [
        table()
    ]
    def generateHTML(self,page):
        page.articles_list = self.fetchall(page.request.session.session_key)
        return super().generateHTML(page)
    def fetchall(self,session):
        self.c.execute('EXECUTE ' + self.sql + '(%s);', [session])
        result = self.c.fetchall()
        return result

With blocks generated from components:
class tableHTML(code):
    def __iter__(self,page=None):
        yield "<table>"
        if page:
            if page.request.user.is_authenticated:
                for row in page.articles_list:
                    yield f'<tr><td class="fixed">{row.date_published:%B %d, %Y}</td><td><a href="/{row.slug}">{row.title}</a></td><td><a href="edit">edit</a><td><a href="delete">delete</a></td></tr>'
            else:
                for row in page.articles_list:
                    yield f'<tr><td>{row.date_published:%B %d, %Y}</td><td><a href="/{row.slug}">{row.title}</a></td></tr>'
        yield "</table>"

class tableCSS(code):
    def __iter__(self,page=None):
        yield "table {border:0};"

class table(Component):
    def __init__(self):
        super().__init__()
        self.html = tableHTML()
        self.css = tableCSS()


With pages created by combining these block classes:


class viewArticle(UnchainedTemplate):

    blocks = [viewArticleHead, article_body]

class index(UnchainedTemplate):

    blocks = [viewIndexHead, article_list]


There are lots of properties of each class that I don’t show. Each Block class has properties for SQL, CSS, Header JS, Body initial JS, Body after JS. The UnchainedTemplate page classes do things like generate the HTML by combining each blocks from classes as well as reading in common HTML header/closer fragments from files.

It also calculates things like Content Security Policy headers for each piece of JS/CSS, and GZip compress each block BEFORE it goes to Redis cache - increasing my Redis cache size by 10x and speeding up page reads by not having to recompress it for every lookup. I also minimize CSS/JS from the class properties.

In the very long term, it’s entirely possible to create smart component-based UI classes that not only generate HTML/CSS/JS, but also things like iOS Swift UI classes, if you wanted to make a native iOS app from this, or a desktop UI app. Ideally there would be a library of pre-built smart components that a developer could use to rapidly make an app. I was inspired by Qt, which I loved and programmed in 10+ years ago. Qt has an interesting sockets/slots mechanism for event modeling.

Anyways is this something that’s interesting for a long-term Django goal?

This is an idea I’ve been lightly exploring a little bit for a couple of years, and is probably several years away from being usable by anyone else.

-bobby

Adam Johnson

unread,
Jul 30, 2019, 1:40:30 PM7/30/19
to django-d...@googlegroups.com
Hi Bobby

Interesting approach. I think Python has had a few experiments with templates as classes/functions before, such as https://pypi.org/project/yattag/ . But you're right, f-strings do make this easier.

If this idea is something that's several years away from usability, I suggest releasing and iterating as a third party template engine - https://docs.djangoproject.com/en/2.2/topics/templates/#support-for-template-engines . I don't know of many in existence but there are two in Django itself (DTL and Jinja2) so that should help. It's better to develop new ideas outside of Django core, because once they're in Django development tends to slow down with the strict deprecation cycle and our aim for stability.

As for redis compression, I believe django-redis already has an option that will help with the cache size - https://niwinz.github.io/django-redis/latest/#_compression_support . But as I understand you added something like pre-compression to Django's FetchFromCacheMiddleware or cache_page decorator as well? That's probably a smart addition Django could use today. If you have a patch, it's worth opening a ticket and starting a PR.

Thanks,

Adam

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/9256593B-4480-408C-8876-FBB1F7A72A2E%40gmail.com.


--
Adam
Reply all
Reply to author
Forward
0 new messages