How to pass parameter to raw sql in Django

Question:

I am trying to create a simple ledger using raw SQL query in Django through my view.py. If I add my parameter value direct I get my required results but if I follow the tutorial here I get the error message below.

ERROR MESSAGE

ProgrammingError at /books/ledger/1005068200/
not enough arguments for format string
Request Method: GET
Request URL:    http://127.0.0.1:8000/books/ledger/1005068200/
Django Version: 4.1.7
Exception Type: ProgrammingError
Exception Value:    
not enough arguments for format string
Exception Location: c:xampphtdocstammgoapp-envLibsite-packagesMySQLdbcursors.py, line 203, in execute....

CODE I HAVE TRIED
views.py

from django.shortcuts import render
from django.core.paginator import Paginator
from django.contrib import messages
from django.db.models import Q
from django.shortcuts import redirect, render, reverse
from django.urls import reverse_lazy
from django.http import HttpResponse
from books.forms import VoucherForm, GlForm, PersonForm, GlgroupForm, AccountForm
from . models import Voucher, Gl, Persons, Glgroup, Account
from django.views import generic
from django.views.generic import (
    CreateView,
    DetailView,
    View,
)


class BookLedgerView(DetailView):
    model = Voucher
    template_name = "books/Vourcher_ledger.html"

    def get(self, request, *args, **kwargs):
        sql = '''SELECT datecreated, accountnumber, vtype, id, accountnumber, datecreated, debit, credit, @balance:= @balance + debit - credit AS balance
            FROM (SELECT datecreated, id, accountnumber, vtype, amount, @balance:=0,
                SUM(CASE WHEN vtype='dr' THEN amount ELSE 0 END) AS debit, 
                SUM(CASE WHEN vtype='cr' THEN amount ELSE 0 END) AS credit
            FROM books_voucher v WHERE accountnumber = %s', [accountnumber]
            GROUP BY id) v
            WHERE id>=id
            ORDER BY id DESC'''
        context = {}
        ledger = Voucher.objects.raw(sql)[:20]
        context = {"ledger": ledger}
        return render(request, "books/vourcher_ledger.html", context=context)

vourcher_ledger.html
This my template


{% extends './base.html' %} 
{% load static i18n%} 
{% load bootstrap5 %} 
{% block content %}
{% load humanize %}
      <table class="table table-bordered table-striped table-sm">
        <thead>
          <tr>
            <th>Date Created</th>
            <th>Description</th>
            <th>Debit</th>
            <th>Crebit</th>
            <th>Balance</th>
          </tr>
        </thead>
        <tbody>
          {% for vourcher in ledger %}
          <tr>
            <td>{{ vourcher.datecreated }}</td>
            <td>{{ vourcher.description }}</td>
            <td class="text-end">{{ vourcher.debit|floatformat:"2"|intcomma }}</td>
            <td class="text-end">{{ vourcher.credit|floatformat:"2"|intcomma }}</td>
            <td class="text-end">{{ vourcher.balance|floatformat:"2"|intcomma }}</td>
          </tr>
          {% endfor %}
        </tbody>
      </table>
      {% endblock content %}

urls.py

from django.contrib.auth.decorators import login_required
from django.urls import path

from books import views

urlpatterns = [
   path("", views.IndexView.as_view(), name="index"),
   path(
        "books/ledger/<accountnumber>/",
        login_required(views.BookLedgerView.as_view()),
        name="book_ledger",
    ),
]

RESULT WHEN I ENTER PARAMETER VALUE DIRECTLY
http://127.0.0.1:8000/books/ledger/1005068200/
enter image description here

Asked By: Mukoro Godwin

||

Answers:

Remove the [accountnumber] parameter from your SQL query. You can unpack the accountnumber from kwargs like this self.kwargs['accountnumber'] and assign it to a new variable for example: accountnumber = self.kwargs['accountnumber'] then pass this new variable into your SQL query as per below:

sql = '''SELECT datecreated, accountnumber, vtype, id, accountnumber, datecreated, debit, credit, @balance:= @balance + debit - credit AS balance
            FROM (SELECT datecreated, id, accountnumber, vtype, amount, @balance:=0,
                SUM(CASE WHEN vtype='dr' THEN amount ELSE 0 END) AS debit, 
                SUM(CASE WHEN vtype='cr' THEN amount ELSE 0 END) AS credit
            FROM books_voucher v WHERE accountnumber = %s
            GROUP BY id) v
            WHERE id>=id
            ORDER BY id DESC'''

Replace your BookLedgerView view class with this updated code below:

class BookLedgerView(DetailView):
    model = Voucher
    template_name = "books/Vourcher_ledger.html"

    def get(self, request, *args, **kwargs):
        accountnumber = self.kwargs['accountnumber']
        sql = '''SELECT datecreated, accountnumber, vtype, id, accountnumber, datecreated, debit, credit, @balance:= @balance + debit - credit AS balance
            FROM (SELECT datecreated, id, accountnumber, vtype, amount, @balance:=0,
                SUM(CASE WHEN vtype='dr' THEN amount ELSE 0 END) AS debit, 
                SUM(CASE WHEN vtype='cr' THEN amount ELSE 0 END) AS credit
            FROM books_voucher v WHERE accountnumber = %s
            GROUP BY id) v
            WHERE id>=id
            ORDER BY id DESC'''
        context = {}
        ledger = Voucher.objects.raw(sql, [accountnumber])[:20]
        context = {"ledger": ledger}
        return render(request, "books/vourcher_ledger.html", context=context)

Note: Using raw SQL queries in Django views is not generally recommended.

Answered By: dostogircse171
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.