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/
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.
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/
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.