Dynamic Column Print Format in Frappe Using User's GridView Preferences

Why Built This

In most ERPNext setups, users personalize their child tables using the GridView settings — selecting only the columns they care about.

But the Print Format still shows all columns — static, cluttered, and not user-friendly.

So I built a dynamic column print format for Purchase Order using the saved GridView settings from _user_settings (with fallback to SQL and Doctype meta).

Here’s how it works :point_down:


Step 1: Python Method to Get Dynamic Columns

This function:

  • Checks the GridView config from cache
  • Falls back to the __UserSettings table
  • Falls back to in_list_view fields with column width from Doctype meta

Final Code

import json
import frappe

def get_dynamic_columns():
    user = frappe.session.user
    doctype = "Purchase Order"
    child_table = "Purchase Order Item"

    meta = frappe.get_meta(child_table)
    fields_map = {f.fieldname: f.label for f in meta.fields}

    def map_fieldnames(fieldnames):
        return [
            {"label": fields_map.get(f), "fieldname": f}
            for f in fieldnames if f in fields_map
        ]

    # 1. From Cache
    user_settings = frappe.cache.hgetall("_user_settings")
    for key, data in user_settings.items():
        key = frappe.safe_decode(key)
        try:
            doc, usr = key.split("::")
        except ValueError:
            continue

        if doc == doctype and usr == user:
            try:
                data = json.loads(data)
                gridview_data = data.get("GridView", {})
                if child_table in gridview_data:
                    fieldnames = [f.get("fieldname") for f in gridview_data[child_table]]
                    return map_fieldnames(fieldnames)
            except Exception:
                continue

    # 2. From SQL
    query = """
        SELECT data FROM `__UserSettings` 
        WHERE user = %s AND doctype = %s
    """
    sql_data = frappe.db.sql(query, (user, doctype), as_dict=True)
    for row in sql_data:
        try:
            parsed_data = json.loads(row["data"])
            gridview_data = parsed_data.get("GridView", {})
            if child_table in gridview_data:
                fieldnames = [f.get("fieldname") for f in gridview_data[child_table]]
                return map_fieldnames(fieldnames)
        except Exception:
            continue

    # 3. Fallback from Meta
    return [
        {"label": f.label, "fieldname": f.fieldname}
        for f in meta.fields
        if f.in_list_view and (f.columns or 0) > 0
    ]

Step 2: Register Method in hooks.py

jinja = {
    "methods": "sudhanshu_app.custom_script.utils.utils.get_dynamic_columns"
}

This makes the method callable in Jinja templates like {{ get_dynamic_columns() }}.


Step 3: The Jinja Print Format

<!-- Purchase Order Print Format -->

<h2>Purchase Order</h2>
<p><strong>Supplier:</strong> {{ doc.supplier }}</p>
<p><strong>Date:</strong> {{ doc.transaction_date }}</p>

<br>

{% set columns = get_dynamic_columns() %}
<table class="table table-bordered table-condensed">
    <thead>
        <tr>
            {% for col in columns %}
                <th>{{ col.label }}</th>
            {% endfor %}
        </tr>
    </thead>
    <tbody>
        {% for item in doc.items %}
            <tr>
                {% for col in columns %}
                    <td>{{ item[col.fieldname] }}</td>
                {% endfor %}
            </tr>
        {% endfor %}
    </tbody>
</table>

Example Case 1 — Grid View with 4 Fields

User saves the following GridView columns in Purchase Order Item:

  • Item Code
  • Quantity
  • UOM
  • Rate (INR)

GridView Settings

case1-s

Print Output

case1o


Example Case 2 — Grid View Changed

User updates the GridView settings:

  • Item Code
  • Item Name
  • Quantity
  • UOM
  • Rate (INR)

GridView Settings

case2s

Print Output

case2o


The print format automatically reflects the change without any code update. That’s the power of dynamic print layouts.



Final Thoughts

This pattern keeps the printed documents as clean and personalized as the UI the user is already working in — no more rigid static tables!


Connect with me on: LinkedIn-Sudhanshu Badole

5 Likes