[Django]-Django: Using an F expression for a text field in an update call

25๐Ÿ‘

You can use the Concat db function.

from django.db.models import Value
from django.db.models.functions import Concat

ATable.objects.filter(id=100).update(some_field=Concat('some_field', Value('more string')))

In my case, I am adding a suffix for facebook avatars URIs like this:

FACEBOOK_URI = 'graph.facebook.com'
FACEBOOK_LARGE = '?type=large'
# ...
users = User.objects.filter(Q(avatar_uri__icontains=FACEBOOK_URI) & ~Q(avatar_uri__icontains=FACEBOOK_LARGE))
users.update(avatar_uri=Concat('avatar_uri', Value(FACEBOOK_LARGE)))

and I get SQL like this (Django 1.9):

UPDATE `user_user` SET `avatar_uri` = CONCAT(COALESCE(`user_user`.`avatar_uri`, ''), COALESCE('?type=large', ''))
WHERE (`user_user`.`avatar_uri` LIKE '%graph.facebook.com%' AND NOT (`user_user`.`avatar_uri` LIKE '%?type=large%' AND `user_user`.`avatar_uri` IS NOT NULL))

The result is all image URIs were changed from http://graph.facebook.com/<fb user id>/picture to http://graph.facebook.com/<fb user id>/picture?type=large

๐Ÿ‘คMaks Skorokhod

23๐Ÿ‘

You can override F object in Django with one simple change:

class CF(F):
    ADD = '||'

Then just use CF in place of F. It will place โ€œ||โ€ instead of โ€œ+โ€ when generating SQL. For example, the query:

User.objects.filter(pk=100).update(email=CF('username') + '@gmail.com')

will generate the SQL:

UPDATE "auth_user" SET "email" = "auth_user"."username" || '@gmail.com'
WHERE "auth_user"."id" = 100 

3๐Ÿ‘

And if you get this running, it isnโ€™t thread safe. While your update is running, some other process can update a model not knowing the data in the database is updated.

You have too acquire a lock, but donโ€™t forget this senario:

  1. Django: m = Model.objects.all()[10]
  2. Django: m.field = field
  3. Django: a progress which takes a while (time.sleep(100))
  4. DB: Lock table
  5. DB: Update field
  6. DD: Unlock table
  7. Django: the slow process is finished
  8. Django: m.save()

Now the field update became undone by the model instance in Django (Ghost write)

๐Ÿ‘คWillian

3๐Ÿ‘

You can achieve this functionality with Djangoโ€™s select_for_update() operator: https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-for-update

Something like this:

obj = ATable.objects.select_for_update().get(id=100)
obj.aField = obj.aField + aStringVar
obj.save()

The table row will be locked when you call .select_for_update().get(), and the lock will be released when you call .save(), allowing you to perform the operation atomically.

๐Ÿ‘คJosh Ourisman

2๐Ÿ‘

seems you canโ€™t do this. however, what you are trying to do could be solved using transactions

(looks like you are using postgres, so if you want to do it in one query and use raw sql as suggested, || is the concatenation operator you want)

๐Ÿ‘คsecond

Leave a comment