Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an idiomatic way to get_or_create then update an object in Django?

I have a Django model called StaffSettings which contains various configuration options for users in my Django app. Each User has at most one entry in the StaffSettings table.

Assume that one setting is default_year_level, and I have code for my user objects like:

def set_default_year_level(u, year_level):
    obj, _created = StaffSettings.objects.get_or_create(user=u)
    obj.default_year_level = year_level
    obj.save()

I would prefer the body of the function to fit onto one line because it seems like a common use case, but if I defined it as

def set_default_year_level(u, year_level):
    StaffSettings.objects.filter(user=u).update(default_year_level=year_level)

which works fine if the user in question already has a row in the StaffSettings table, but it won't create the relevant row if it doesn't exist.

What is the idiomatic/best way to code this? (e.g. Is there some sort of filter_or_create function? Or do other people write decorators/helper functions to handle this idiom?)

like image 944
bryn Avatar asked Mar 18 '13 09:03

bryn


2 Answers

I don't see any problem with your first function, I would have written the same for this usecase.

However if you need the same feature on a lot of fields on your model and you don't want to repeat yourself you can pass the field as parameter :

def set_default_value(u, field, value):
  obj, _created = StaffSettings.objects.get_or_create(user=u)
  setattr(obj, field, value)
  obj.save()

And I will stay away from the update() function anyway as this function is meant to update multiple objects at once and does not trigger the save() method nor signals on your models (see https://docs.djangoproject.com/en/dev/topics/db/queries/#updating-multiple-objects-at-once)

like image 64
Ponytech Avatar answered Nov 19 '22 04:11

Ponytech


Since Django 1.7 you can use update_or_create():

def set_default_year_level(u, year_level):
    obj, _created = StaffSettings.objects.update_or_create(
        user=u,
        default_year_level=year_level
    )

Or, for a more generic case as explained in the previous answer:

def set_default_values(u, **kwargs):
    obj, _created = StaffSettings.objects.update_or_create(user=u, defaults=kwargs)

which also achieves your additional requirement

I would prefer the body of the function to fit onto one line

like image 31
LostMyGlasses Avatar answered Nov 19 '22 04:11

LostMyGlasses