Django transaction.atomic() guarantees atomic READ + WRITE? - python

I need to make sure that an object that is read from the database and written back, cannot be modified in the meantime by another request/process.
Does transaction.atomic() guarantee that?
My tests so far tell me no. If there's nothing wrong with them what would be the right way to achieve atomic READS and WRITES?
My example that I have tested.
Put the Test class somewhere in your model. atomic_test.py and atomic_test2.py should be saved as management commands. Run python manage.py atomic_test first, then python manage.py atomic_test2. The second script does not block and its changes are lost.
models.py
class Test(models.Model):
value = models.IntegerField()
atomic_test.py
from django.core.management.base import NoArgsCommand
from django.db import transaction
from time import sleep
from core.models import Test
class Command(NoArgsCommand):
option_list = NoArgsCommand.option_list
def handle(self, **options):
Test.objects.all().delete()
t = Test(value=50)
t.save()
print '1 started'
with transaction.atomic():
t = Test.objects.all()[0]
sleep(10)
t.value = t.value + 10
t.save()
print '1 finished: %s' %Test.objects.all()[0].value
atomic_test2.py
from django.core.management.base import NoArgsCommand
from django.db import transaction
from time import sleep
from core.models import Test
class Command(NoArgsCommand):
option_list = NoArgsCommand.option_list
def handle(self, **options):
print '2 started'
with transaction.atomic():
t = Test.objects.all()[0]
t.value = t.value - 20
t.save()
print '2 finished: %s' %Test.objects.all()[0].value

Django's transaction.atomic() is a thin abstraction over the transaction facilities of the database. So its behavior really depends on the database layer, and is specific to the type of database and its settings. So to really understand how this works you need to read and understand the transaction documentation for your database. (In PostgreSQL, for example, the relevant documentation is Transaction Isolation and Explicit Locking).
As for your specific test case, the behavior you want can be achieved by using the select_for_update() method on a Django queryset (if your database supports it). Something like:
in atomic_test.py
with transaction.atomic():
t = Test.objects.filter(id=1).select_for_update()[0]
sleep(10)
t.value = t.value + 10
t.save()
in atomic_test2.py
with transaction.atomic():
t = Test.objects.filter(id=1).select_for_update()[0]
t.value = t.value - 20
t.save()
The second one should block until the first one finishes, and see the new value of 60.
Other options include using the SERIALIZABLE transaction isolation level or using a row lock, though Django doesn't provide a convenient API for doing those things.

It would be much better to update atomically on the database using the F function:
from django.db.models import F
Test.objects.filter(id=1).update(value=F("value") + 10)
(This generates SQL something like "UPDATE test_test SET value = value + 10 WHERE id = 1")

Related

Python: Calculate time between current time and last login. (Automated Communication)

I'm trying to make a celery task that would send a basic reminder to our users. So in our automated communication project, we have these tasks:
As you can see there are few actions that are different. So for now I have created a logic that fetches all the users from the DB and then continues by checking the time difference. But for now, I only have set-up for 2 hours or more. How should I use it correctly? I do not want to re-write each if statement because it's bad practice. How should I make it clear and reduce the system load?
#app.task
def check_registered_users():
from apps.users.models import User
from apps.notifications.models import AutomatedCommunicationNotifications
day_start = datetime.utcnow().date()
day_end = day_start + timedelta(days=1)
users = User.objects.filter(is_active=True, date_joined__range=(day_start, day_end))
users_that_received_notification = AutomatedCommunicationNotifications.objects.all().values('user__id')
excluded_users = users.exclude(id__in=users_that_received_notification)
for user in excluded_users:
if user.last_login < user.last_login + timedelta(hours=2):
# Sign-up uncompleted Push notification 2 hours after last login
template = SiteConfiguration.get_solo().automated_comms_signup_uncompleted
send_plain_email_task(
email=user.email,
subject=template.subject,
html_message=template.content,
from_email=f'{settings.EMAIL_FROM_PREFIX} <{settings.DEFAULT_FROM_EMAIL}>',
)
P.S AutomatedCommunicationNotifications table is for us to track which user has already received a notification.
class AutomatedCommunicationNotifications(BaseModel):
""" Model for automated comms notifications """
user = models.ForeignKey(User, on_delete=models.CASCADE)
type = models.CharField(
max_length=255,
choices=NotificationTypes.get_choices(),
default=NotificationTypes.EMAIL_NOTIFICATION
)
def __str__(self):
return str(self.user.phone)
You'll have to iterate over your queried users at least once but here are tips that may help:
models.py
class User(...):
# add a field to determine if the user has registered or not
# set this to `True` when a User successfully registers:
is_registered = models.BooleanField(default=False)
class AutomatedCommunicationNotifications(BaseModel):
# add a related name field for easier coding:
user = models.ForeignKey(..., related_name = 'notifications')
tasks.py
# load packages outside of your function so this only runs once on startup:
from django.models import F
from apps.users.models import User
from apps.notifications.models import AutomatedCommunicationNotifications
#app.task
def check_registered_users():
# timestamps:
two_hours_ago = datetime.now() - timedelta(hours=2)
# query for unregistered users who have not received a notification:
users = User.objects.filter(
is_registered = False,
last_login__lt = two_hours_ago # last logged in 2 or more hours ago
).exclude(
notifications__type = "the type"
).prefetch_related(
'notifications' # prejoins tables to improve performance
)
for user in users:
# send email
...
I would do this with a cron job. You can let it run whenever you want, depends on how fast after your give time frame you want to sent this.
You start with making a folder in your app:
/django/yourapp/management/commands
There you make a python file which contains your logic. Make sure to import the right modules from your views.
from django.core.management.base import BaseCommand, CommandError
from yourapp.models import every, module, you, need
from django.utils import timezone
from datetime import datetime, date, timedelta
from django.core.mail import send_mail, EmailMessage
class Command(BaseCommand):
help = 'YOUR HELP TEXT FOR INTERNAL USE'
def handle(self, *args, **options):
# Your logic
I added the crontab to the www-data users crontab like this:
# m h dom mon dow command
45 3 * * * /websites/vaccinatieplanner/venv/bin/python /websites/vaccinatieplanner/manage.py reminder
You can use that crontab file to tweak your optimal time between checks. If you remove the 3 and replace it by a * then you will have it check every 45 mins.

how to access field using django signals

I would like to check the value of a field (lightStatusA) when a new record is saved into my Django database. I feel like iv'e read the docs 10 times and still can't grasp how to get this. Here is my current models.py code:
from django.db import models
from accounts.models import Customer
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models.signals import post_save
class Data(models.Model):
author = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,)
tempData= models.CharField(max_length=50,blank=True,)
humidData= models.CharField(max_length=50,blank=True,)
lightStatusA= models.BooleanField(default=True,)
dateTime = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.author)
def check_light_status(sender, **kwargs):
if kwargs['created']: #make sure its a new record
#need logic to grab instance.lightStatusA and check it's value
post_save.connect(check_light_status, sender = Data)
Is there some way of passing this value as an argument to the signal?
The check_light_status function can accept an instance parameter.
From the docs:
instance
The actual instance being saved.
Update: You said this:
instance returns the author of the post.
I am going to use my powers of deduction to guess that you tried print(instance) and saw the author. Look at your __str__ implementation.
def __str__(self):
return str(self.author)
I'd say you sabotaged yourself a bit there ;)
Ahh ok got it:
def check_light_status(sender, instance, **kwargs):
if kwargs['created']: #make sure its a new record
print(instance.lightStatusA)
This prints the the field I need to run some logic against.

Dereference Models from many to many relationship

In my schema, as described in the below test data generation example, I want to know a good way to:
Dereference all instances of Favourites that have reference keys to instances of Pictures that have been deleted. Just delete any Favourite that links to a deleted picture.
The Person class is a user
The Picture class is something that can be a Favourite
The Favourite class is an example of the Link-Model way of having many-to-many relationships.
Why this question?
First I hope it doesn't fall out of the scope here, second because this can happen and third because it's interesting.
How?
Let's say that a person can have up to thousands favourites, something like Likes are on social networks or to make it worse, orders, accounts or invalid data in a scientific application.
In our example for some reason (and these reasons happen) a person is experiencing lot of dead favourite link, or I do know, that there are dead favourites.
What would be a good way to do this, reducing ndb.get() operations and not iterating through every Favourite.
Lets not complicate things. Lets make the assumption that we have only one user suffering from dead favourites. He has a class of Person and stubbed user_id property of '123'.
In the following example you can use the following handlers and their corresponding functions.
import time
import sys
import logging
import random
import cgi
import webapp2
from google.appengine.ext import ndb
class Person(ndb.Expando):
pass
class Picture(ndb.Expando):
pass
class Favourite(ndb.Expando):
user_id = ndb.StringProperty(required=True)
#picture = ndb.KeyProperty(kind=Picture, required=True)
pass
class GenerateDataHandler(webapp2.RequestHandler):
def get(self):
try:
number_of_models = abs(int(cgi.escape(self.request.get('n'))))
except:
number_of_models = 10
logging.info("GET ?n=parameter not defined. Using default.")
pass
user_id = '123' #stub
person = Person.query().filter(ndb.GenericProperty('user_id') == user_id).get()
if not person:
person = Person()
person.user_id = user_id #Stub
person.put()
logging.info("Created Person instance")
if not self._gen_data(person, number_of_models):
return
self.response.write("Data generated successfully")
def _gen_data(self, person, number_of_models):
first, last = Picture.allocate_ids(number_of_models)
picture_keys = [ndb.Key(Picture, id) for id in range(first, last+1)]
pictures = []
favourites = []
for picture_key in picture_keys:
picture = Picture(key=picture_key)
pictures.append(picture)
favourite = Favourite(parent=person.key,
user_id=person.user_id,
picture=picture_key
)
favourites.append(favourite)
entities = favourites
entities[1:1] = pictures
ndb.put_multi(entities)
return True
class CorruptDataHandler(webapp2.RequestHandler):
def get(self):
if not self._corrupt_data(0.5):#50% corruption
return
self.response.write("Data corruption completed successfully")
def _corrupt_data(self, n):
picture_keys = Picture.query().fetch(99999, keys_only=True)
random_picture_keys = random.sample(picture_keys, int(float(len(picture_keys))*n))
ndb.delete_multi(random_picture_keys)
return True
class FixDataHandler(webapp2.RequestHandler):
def get(self):
user_id = '123' #stub
person = Person.query().filter(ndb.GenericProperty('user_id') == user_id).get()
self._dereference(person)
def _dereference(self, person):
#Here if where you implement your answer
Separate handlers due to eventual consistency in
the NDB Datastore. More info:
GAE put_multi() entities using backend NDB
Of course I am posting an answer as well to show that I tried something before posting this.
A ReferenceProperty is just a key, so if you have the key of the deleted Person, you can use that to query the Favourite.
Otherwise, there's no easy way. You'll have to filter through all Favourites and find ones that have an invalid Picture. It's very simple in a mapreduce job, but could be an expensive query if you have a lot of Favourites.
You could use a pre delete hook (look here for a way to implement it)
Of course this could be done easier if you use the NDB API instead of the Datastore API (hooks on NDB), but then you'll have to change the way you make the referenes

How to change db for whole block of code

Is there a way to change database for whole block of code. For example:
with using_db('my_other_db_conf'):
MyModel.objects.all()
which would be equivalent of:
MyModel.objects.using('my_other_db_conf').all()
I just need to use different DB depending on context and don't like the idea of using using() method every time :\
I'd use managers. In your models.py:
class DB_one_ItemsManager(models.Manager):
def get_query_set(self):
return super(DB_one_ItemsManager, self).get_query_set().using("database1")
class DB_two_ItemsManager(models.Manager):
def get_query_set(self):
return super(DB_two_ItemsManager, self).get_query_set().using("database2")
class YourModel(models.Model):
#Some fields here
#...
objects_db_one=DB_one_ItemsManager()
objects_db_two=DB_two_ItemsManager()
Or if you want to use objects_db_one or objects_db_two as default manager simply rename it to objects
The behavior needs to modify some global value, IMO, thus with statement is not the apropriate way. Of course, it can be done, in a implicit, dirty and thread unsafe way:
from contextlib import contextmanager
#contextmanager
def unsafe_modify_queryset_db(model_dbs):
"""model_dbs => sequence of tuple (model, db for the model to use).
For example ((User, 'slice_2'), ...)
"""
prev_db = map(lambda x:x[0].objects._db , model_dbs)
for model, db in model_dbs:
model.objects._db = db
yield
# restore previous db
for x, db in zip(model_dbs, prev_db):
x[0].objects._db = prev_db
# then
with unsafe_modify_queryset_db((User, 'slice_2', ...)):
User.objects.filter(...)
You could also use db_manager to operate on QuerySet level, which achieves the same goal as luke14free's code:
qs = User.objects.db_manager('slice_2')
foo = qs.filter(...)
bar = qs.filter(...)
Remember that Explicit is better than implicit, just arrange your code and enclose querysets sharing same db to function call, would be better.

How to see the raw SQL queries Django is running?

Is there a way to show the SQL that Django is running while performing a query?
See the docs FAQ: "How can I see the raw SQL queries Django is running?"
django.db.connection.queries contains a list of the SQL queries:
from django.db import connection
print(connection.queries)
Querysets also have a query attribute containing the query to be executed:
print(MyModel.objects.filter(name="my name").query)
Note that the output of the query is not valid SQL, because:
"Django never actually interpolates the parameters: it sends the query and the parameters separately to the database adapter, which performs the appropriate operations."
From Django bug report #17741.
Because of that, you should not send query output directly to a database.
If you need to reset the queries to, for example, see how many queries are running in a given period, you can use reset_queries from django.db:
from django.db import reset_queries
from django.db import connection
reset_queries()
# Run your query here
print(connection.queries)
>>> []
Django-extensions have a command shell_plus with a parameter print-sql
./manage.py shell_plus --print-sql
In django-shell all executed queries will be printed
Ex.:
User.objects.get(pk=1)
SELECT "auth_user"."id",
"auth_user"."password",
"auth_user"."last_login",
"auth_user"."is_superuser",
"auth_user"."username",
"auth_user"."first_name",
"auth_user"."last_name",
"auth_user"."email",
"auth_user"."is_staff",
"auth_user"."is_active",
"auth_user"."date_joined"
FROM "auth_user"
WHERE "auth_user"."id" = 1
Execution time: 0.002466s [Database: default]
<User: username>
Take a look at debug_toolbar, it's very useful for debugging.
Documentation and source is available at http://django-debug-toolbar.readthedocs.io/.
The query is actually embedded in the models API:
q = Query.objects.values('val1','val2','val_etc')
print(q.query)
No other answer covers this method, so:
I find by far the most useful, simple, and reliable method is to ask your database. For example on Linux for Postgres you might do:
sudo su postgres
tail -f /var/log/postgresql/postgresql-8.4-main.log
Each database will have slightly different procedure. In the database logs you'll see not only the raw SQL, but any connection setup or transaction overhead django is placing on the system.
Though you can do it with with the code supplied, I find that using the debug toolbar app is a great tool to show queries. You can download it from github here.
This gives you the option to show all the queries ran on a given page along with the time to query took. It also sums up the number of queries on a page along with total time for a quick review. This is a great tool, when you want to look at what the Django ORM does behind the scenes. It also have a lot of other nice features, that you can use if you like.
Another option, see logging options in settings.py described by this post
http://dabapps.com/blog/logging-sql-queries-django-13/
debug_toolbar slows down each page load on your dev server, logging does not so it's faster. Outputs can be dumped to console or file, so the UI is not as nice. But for views with lots of SQLs, it can take a long time to debug and optimize the SQLs through debug_toolbar since each page load is so slow.
This is a much late answer but for the others are came here by searching.
I want to introduce a logging method, which is very simple; add django.db.backends logger in settins.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}
I am also using an environment variable to set the level.
So when I want to see the SQL queries I just set the environment variable, and debug log shows the actual queries.
I developed an extension for this purpose, so you can easily put a decorator on your view function and see how many queries are executed.
To install:
$ pip install django-print-sql
To use as context manager:
from django_print_sql import print_sql
# set `count_only` to `True` will print the number of executed SQL statements only
with print_sql(count_only=False):
# write the code you want to analyze in here,
# e.g. some complex foreign key lookup,
# or analyzing a DRF serializer's performance
for user in User.objects.all()[:10]:
user.groups.first()
To use as decorator:
from django_print_sql import print_sql_decorator
#print_sql_decorator(count_only=False) # this works on class-based views as well
def get(request):
# your view code here
Github: https://github.com/rabbit-aaron/django-print-sql
If you make sure your settings.py file has:
django.core.context_processors.debug listed in CONTEXT_PROCESSORS
DEBUG=True
your IP in the INTERNAL_IPS tuple
Then you should have access to the sql_queries variable. I append a footer to each page that looks like this:
{%if sql_queries %}
<div class="footNav">
<h2>Queries</h2>
<p>
{{ sql_queries|length }} Quer{{ sql_queries|pluralize:"y,ies" }}, {{sql_time_sum}} Time
{% ifnotequal sql_queries|length 0 %}
(<span style="cursor: pointer;" onclick="var s=document.getElementById('debugQueryTable').style;s.disp\
lay=s.display=='none'?'':'none';this.innerHTML=this.innerHTML=='Show'?'Hide':'Show';">Show</span>)
{% endifnotequal %}
</p>
<table id="debugQueryTable" style="display: none;">
<col width="1"></col>
<col></col>
<col width="1"></col>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">SQL</th>
<th scope="col">Time</th>
</tr>
</thead>
<tbody>
{% for query in sql_queries %}
<tr class="{% cycle odd,even %}">
<td>{{ forloop.counter }}</td>
<td>{{ query.sql|escape }}</td>
<td>{{ query.time }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
I got the variable sql_time_sum by adding the line
context_extras['sql_time_sum'] = sum([float(q['time']) for q in connection.queries])
to the debug function in django_src/django/core/context_processors.py.
Just to add, in django, if you have a query like:
MyModel.objects.all()
do:
MyModel.objects.all().query.sql_with_params()
or:
str(MyModel.objects.all().query)
to get the sql string
Django SQL Sniffer is another alternative for viewing (and seeing the stats of) raw executed queries coming out of any process utilising Django ORM. I've built it to satisfy a particular use-case that I had, which I haven't seen covered anywhere, namely:
no changes to the source code that the target process is executing (no need to register a new app in django settings, import decorators all over the place etc.)
no changes to logging configuration (e.g. because I'm interested in one particular process, and not the entire process fleet that the configuration applies to)
no restarting of target process needed (e.g. because it's a vital component, and restarts may incur some downtime)
Therefore, Django SQL Sniffer can be used ad-hoc, and attached to an already running process. The tool then "sniffs" the executed queries and prints them to console as they are executed. When the tool is stopped a statistical summary is displayed with outlier queries based on some possible metric (count, max duration and total combined duration).
Here's a screenshot of an example where I attached to a Python shell
You can check out the live demo and more details on the github page.
I put this function in a util file in one of the apps in my project:
import logging
import re
from django.db import connection
logger = logging.getLogger(__name__)
def sql_logger():
logger.debug('TOTAL QUERIES: ' + str(len(connection.queries)))
logger.debug('TOTAL TIME: ' + str(sum([float(q['time']) for q in connection.queries])))
logger.debug('INDIVIDUAL QUERIES:')
for i, query in enumerate(connection.queries):
sql = re.split(r'(SELECT|FROM|WHERE|GROUP BY|ORDER BY|INNER JOIN|LIMIT)', query['sql'])
if not sql[0]: sql = sql[1:]
sql = [(' ' if i % 2 else '') + x for i, x in enumerate(sql)]
logger.debug('\n### {} ({} seconds)\n\n{};\n'.format(i, query['time'], '\n'.join(sql)))
Then, when needed, I just import it and call it from whatever context (usually a view) is necessary, e.g.:
# ... other imports
from .utils import sql_logger
class IngredientListApiView(generics.ListAPIView):
# ... class variables and such
# Main function that gets called when view is accessed
def list(self, request, *args, **kwargs):
response = super(IngredientListApiView, self).list(request, *args, **kwargs)
# Call our function
sql_logger()
return response
It's nice to do this outside the template because then if you have API views (usually Django Rest Framework), it's applicable there too.
The following returns the query as valid SQL, based on https://code.djangoproject.com/ticket/17741:
def str_query(qs):
"""
qs.query returns something that isn't valid SQL, this returns the actual
valid SQL that's executed: https://code.djangoproject.com/ticket/17741
"""
cursor = connections[qs.db].cursor()
query, params = qs.query.sql_with_params()
cursor.execute('EXPLAIN ' + query, params)
res = str(cursor.db.ops.last_executed_query(cursor, query, params))
assert res.startswith('EXPLAIN ')
return res[len('EXPLAIN '):]
I believe this ought to work if you are using PostgreSQL:
from django.db import connections
from app_name import models
from django.utils import timezone
# Generate a queryset, use your favorite filter, QS objects, and whatnot.
qs=models.ThisDataModel.objects.filter(user='bob',date__lte=timezone.now())
# Get a cursor tied to the default database
cursor=connections['default'].cursor()
# Get the query SQL and parameters to be passed into psycopg2, then pass
# those into mogrify to get the query that would have been sent to the backend
# and print it out. Note F-strings require python 3.6 or later.
print(f'{cursor.mogrify(*qs.query.sql_with_params())}')
There's another way that's very useful if you need to reuse the query for some custom SQL. I've used this in an analytics app that goes far beyond what Django's ORM can do comfortably, so I'm including ORM-generated SQL as subqueries.
from django.db import connection
from myapp.models import SomeModel
queryset = SomeModel.objects.filter(foo='bar')
sql_query, params = queryset.query.as_sql(None, connection)
This will give you the SQL with placeholders, as well as a tuple with query params to use. You can pass this along to the DB directly:
with connection.connection.cursor(cursor_factory=DictCursor) as cursor:
cursor.execute(sql_query, params)
data = cursor.fetchall()
I've made a small snippet you can use:
from django.conf import settings
from django.db import connection
def sql_echo(method, *args, **kwargs):
settings.DEBUG = True
result = method(*args, **kwargs)
for query in connection.queries:
print(query)
return result
# HOW TO USE EXAMPLE:
#
# result = sql_echo(my_method, 'whatever', show=True)
It takes as parameters function (contains sql queryies) to inspect and args, kwargs needed to call that function. As the result it returns what function returns and prints SQL queries in a console.
To get result query from django to database(with correct parameter substitution)
you could use this function:
from django.db import connection
def print_database_query_formatted(query):
sql, params = query.sql_with_params()
cursor = connection.cursor()
cursor.execute('EXPLAIN ' + sql, params)
db_query = cursor.db.ops.last_executed_query(cursor, sql, params).replace('EXPLAIN ', '')
parts = '{}'.format(db_query).split('FROM')
print(parts[0])
if len(parts) > 1:
parts = parts[1].split('WHERE')
print('FROM{}'.format(parts[0]))
if len(parts) > 1:
parts = parts[1].split('ORDER BY')
print('WHERE{}'.format(parts[0]))
if len(parts) > 1:
print('ORDER BY{}'.format(parts[1]))
# USAGE
users = User.objects.filter(email='admin#admin.com').order_by('-id')
print_database_query_formatted(users.query)
Output example
SELECT "users_user"."password", "users_user"."last_login", "users_user"."is_superuser", "users_user"."deleted", "users_user"."id", "users_user"."phone", "users_user"."username", "users_user"."userlastname", "users_user"."email", "users_user"."is_staff", "users_user"."is_active", "users_user"."date_joined", "users_user"."latitude", "users_user"."longitude", "users_user"."point"::bytea, "users_user"."default_search_radius", "users_user"."notifications", "users_user"."admin_theme", "users_user"."address", "users_user"."is_notify_when_buildings_in_radius", "users_user"."active_campaign_id", "users_user"."is_unsubscribed", "users_user"."sf_contact_id", "users_user"."is_agree_terms_of_service", "users_user"."is_facebook_signup", "users_user"."type_signup"
FROM "users_user"
WHERE "users_user"."email" = 'admin#admin.com'
ORDER BY "users_user"."id" DESC
It based on this ticket comment: https://code.djangoproject.com/ticket/17741#comment:4
To generate SQL for CREATE / UPDATE / DELETE / commands, which are immediate in Django
from django.db.models import sql
def generate_update_sql(queryset, update_kwargs):
"""Converts queryset with update_kwargs
like : queryset.update(**update_kwargs) to UPDATE SQL"""
query = queryset.query.clone(sql.UpdateQuery)
query.add_update_values(update_kwargs)
compiler = query.get_compiler(queryset.db)
sql, params = compiler.as_sql()
return sql % params
from django.db.models import sql
def generate_delete_sql(queryset):
"""Converts select queryset to DELETE SQL """
query = queryset.query.chain(sql.DeleteQuery)
compiler = query.get_compiler(queryset.db)
sql, params = compiler.as_sql()
return sql % params
from django.db.models import sql
def generate_create_sql(model, model_data):
"""Converts queryset with create_kwargs
like if was: queryset.create(**create_kwargs) to SQL CREATE"""
not_saved_instance = model(**model_data)
not_saved_instance._for_write = True
query = sql.InsertQuery(model)
fields = [f for f in model._meta.local_concrete_fields if not isinstance(f, AutoField)]
query.insert_values(fields, [not_saved_instance], raw=False)
compiler = query.get_compiler(model.objects.db)
sql, params = compiler.as_sql()[0]
return sql % params
Tests & usage
def test_generate_update_sql_with_F(self):
qs = Event.objects.all()
update_kwargs = dict(description=F('slug'))
result = generate_update_sql(qs, update_kwargs)
sql = "UPDATE `api_event` SET `description` = `api_event`.`slug`"
self.assertEqual(sql, result)
def test_generate_create_sql(self):
result = generate_create_sql(Event, dict(slug='a', app='b', model='c', action='e'))
sql = "INSERT INTO `api_event` (`slug`, `app`, `model`, `action`, `action_type`, `description`) VALUES (a, b, c, e, , )"
self.assertEqual(sql, result)
from django.db import reset_queries, connection
class ShowSQL(object):
def __enter__(self):
reset_queries()
return self
def __exit__(self, *args):
for sql in connection.queries:
print('Time: %s\nSQL: %s' % (sql['time'], sql['sql']))
Then you can use:
with ShowSQL() as t:
some queries <select>|<annotate>|<update> or other
it prints
Time: %s
SQL: %s
You can use connection.queries to get the raw SQL queries running in Django as shown below:
# "store/views.py"
from django.db import transaction
from .models import Person
from django.db import connection
from django.http import HttpResponse
#transaction.atomic
def test(request):
Person.objects.create(name="John") # INSERT
qs = Person.objects.select_for_update().get(name="John") # SELECT FOR UPDATE
qs.name = "Tom"
qs.save() # UPDATE
qs.delete() # DELETE
for query in connection.queries: # Here
print(query)
return HttpResponse("Test")
Then, the raw queries are printed on console as shown below:
{'sql': 'INSERT INTO "store_person" ("name") VALUES (\'John\') RETURNING "store_person"."id"', 'time': '0.000'}
{'sql': 'SELECT "store_person"."id", "store_person"."name" FROM "store_person" WHERE "store_person"."name" = \'John\' LIMIT 21 FOR UPDATE', 'time': '0.000'}
{'sql': 'UPDATE "store_person" SET "name" = \'Tom\' WHERE "store_person"."id" = 179', 'time': '0.000'}
{'sql': 'DELETE FROM "store_person" WHERE "store_person"."id" IN (179)', 'time': '0.000'}
[24/Dec/2022 06:29:32] "GET /store/test/ HTTP/1.1" 200 9
Then, put reset_queries() after Person.objects.select_for_update() if you want to get only UPDATE and DELETE queries without INSERT and SELECT FOR UPDATE queries as shown below:
# "store/views.py"
from django.db import transaction
from .models import Person
from django.db import reset_queries
from django.db import connection
from django.http import HttpResponse
#transaction.atomic
def test(request):
Person.objects.create(name="John") # INSERT
qs = Person.objects.select_for_update().get(name="John") # SELECT FOR UPDATE
reset_queries() # Here
qs.name = "Tom"
qs.save() # UPDATE
qs.delete() # DELETE
for query in connection.queries: # Here
print(query)
return HttpResponse("Test")
Then, only UPDATE and DELETE queries are printed without INSERT and SELECT FOR UPDATE queries as shown below:
{'sql': 'UPDATE "store_person" SET "name" = \'Tom\' WHERE "store_person"."id" = 190', 'time': '0.000'}
{'sql': 'DELETE FROM "store_person" WHERE "store_person"."id" IN (190)', 'time': '0.000'}
[24/Dec/2022 07:00:01] "GET /store/test/ HTTP/1.1" 200 9
Several great answers here already.
One more way.
In test, do something like this:
with self.assertNumQueries(3):
response = self.client.post(reverse('payments:pay_list'))
# or whatever
If the number of queries is wrong, the test fails and prints all the raw SQL queries in the console.
Also, such tests help to control that the number of SQL queries does not grow as the code changes and the database load does not get excessive.
View Queries using django.db.connection.queries
from django.db import connection
print(connection.queries)
Access raw SQL query on QuerySet object
qs = MyModel.objects.all()
print(qs.query)
For Django 2.2:
As most of the answers did not helped me much when using ./manage.py shell. Finally i found the answer. Hope this helps to someone.
To view all the queries:
from django.db import connection
connection.queries
To view query for a single query:
q=Query.objects.all()
q.query.__str__()
q.query just displaying the object for me.
Using the __str__()(String representation) displayed the full query.

Categories