• How to Handle Pluralization and Internationalization in Django Templates

    This is written in the docs, but it was a first time for me to handle. Your templates can start to get very verbose when you really start supporting i18n support.Β 

    For strings directly in your templates you can use the blocktrans plural tag. ( Note this changes a bit with Django 3.2, blocktrans becomes blocktranslate ).

    {% load i18n %}
    {% blocktrans count counter=object_list|length %}
    {{ object_list }}δ»Ά
    {% plural %}
    {{ object_list }}δ»Ά
    {% endblocktrans %}

    For master data that has a dedicated DB column, you can use the get_language_code from the i18n package.

    {% get_current_language as LANGUAGE_CODE %}
    {% if LANGUAGE_CODE != "en" %}
        {{ my_model.foo }}
    {% else %}
        {{ my_model.foo_en }}
    {% endif %}
  • TIL: English and GEOS Reference Points Opposite of Each Other

    This post is less a TIL and more of a I knew that and I don't want to forget it again and stems from a bugfix in Tanzawa.

    In English when we refer to a geo-coordinate we usually say it in latitude, longitude order. The reason why we say coordinates in this order is we could measure latitude accurately (via astronomical measurements) before longitude. Frontend mapping libraries like leaflet.js keep this familiar ordering. i.e. plotting points on a map takes a latitude/longitude array and events have a latlng property for referencing points.

    GEOS, the open source geometry library used in most GIS (include GeoDjango) applications doesn't think of points in those terms, but as a graph of x,y coordinates.Β  As such if you when you're working with data across these boundaries it's important to not mix up your ordering.

    When instantiating a Point it's tempting to just pass in floats directly. But if you do that it's easy to mix up the ordering , so I've started make sure I always use the keyword argument name to reduce mistakes.

    from django.contrib.gis.geos import Point
    
    # Keep our familiar lat/lon ordering without messing up the data point.
    point = Point(y=35.31593281000502, x=139.4700015160363)
  • How to Pipe Python stdout with xargs

    When writing instructions for getting started with Tanzawa, users needed a way to set a unique SECRET_KEY in their environment variable configuration file. Initially I had a secret key entry in the sample file with some instructions to "just modify it". But that felt like I was just passing the buck.

    What I wanted to do was to generate a unique secret_key and output it to the .env file. Outputting just the secret key is simple, you can just use >> and append output to an existing file. But I wanted to use my Python secret key output as an argument to another command.

    I did it as follows:

    python3 -c "import secrets; print(secrets.token_urlsafe())" | xargs -I{} -n1 echo SECRET_KEY={} >> .env

    1. Use the Python secrets module to generate a secure token.
    2. Pipe the output to xargs.
    3. -I is "replace string" and "{}" is the string we want xargs to replace. -n1 limits us to a single argument.
    4. xargs executes and takes our Python output as an argument and replaces the {} with it, giving us our desired string.

    Writing this now, I probably could have just used Python to include the SECRET_KEY= bit and forgone using xargs, but it was good practice anyways.
  • How to Process Tailwind / PostCSS with Webpack

    Usually when I work with webpack another tool I'm using generates the appropriate config for me and I can remain blissfully unaware of how it all works.

    With Tanzawa I've only been adding tooling when absolutely necessary. The other day I configured PostCSS and Tailwind with Webpack. It still required a bunch of searching and piecing together blog posts to get something that worked for me.

    Below is a list of facts that helped me figure out how to think about processing CSS with webpack.

    • As wrong as it feels, your entry point for processing your CSS should be a Javascript file.
    • Webpack by default does not output a separate stylesheet file. In order to output a plain CSS file, you must use the MiniCssExtractPlugin.
    • Despite wanting to output only CSS, and specifying the filename in the options (style.css), Webpack will create an empty Javascript file regardless. There isn't a way to prevent this unless you add another plugin. I'm adding it to .gitignore.
    • The "use" plugins have the following roles
      • MiniCssExtractPlugin: Exact built css to a dedicated css file.
      • css-loader: Allow you to import css from your entrypoint Javascript file
      • postcss-loader: Run postcss with yourΒ 

    // webpack.config.js
    const path = require('path');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    const tailwindConfig = {
      entry: "./css/index.js",
      output: {
        path: path.resolve(__dirname, "../static/tailwind/"),
      },
      plugins: [
        new MiniCssExtractPlugin({ 
            filename: "style.css"
        }),
      ],
      module: {
       rules: [
        {
          test: /\.css$/,
            use: [
              MiniCssExtractPlugin.loader,
              { loader: "css-loader", options: { importLoaders: 1 } },
              "postcss-loader",
            ],
        },
       ],
      },
    }
    module.exports = [tailwindConfig]

    In order for postcss to play well with Tailwind and webpack, I needed to update my config to pass the tailwind plugin the path for the tailwind.config.js. It simply imports (requires) the tailwind config and immediately passes the path.

    // postcss.config.js
    module.exports = {
      plugins: [
        require("tailwindcss")("./tailwind.config.js"),
        require("autoprefixer"),
      ]
    }

    Finally to run this all for production, I execute via webpack as follows. I still need to figure out how to get the NODE_ENV environment variable set via with the webpack --production flag, so for now it's a bit redundant.

    // package.json
    {
      ...
      "scripts": {
        "build": "NODE_ENV=production webpack --mode=production"
      }
      ...
    }