ChatGPT解决这个技术问题 Extra ChatGPT

How to update multiple fields of a django model instance?

I'm wondering, what is a standard way of updating multiple fields of an instance of a model in django? ... If I have a model with some fields,

Class foomodel(models.Model):
    field1 = models.CharField(max_length=10)
    field2 = models.CharField(max_length=10)
    field3 = models.CharField(max_length=10)
    ...

... and I instantiate it with one field given, and then in a separate step I want to provide the rest of the fields, how do I do that by just passing a dictionary or key value params? Possible?

In other words, say I have a dictionary with some data in it that has everything I want to write into an instance of that model. The model instance has been instantiated in a separate step and let's say it hasn't been persisted yet. I can say foo_instance.field1 = my_data_dict['field1'] for each field, but something tells me there should be a way of calling a method on the model instance where I just pass all of the field-value pairs at once and it updates them. Something like foo_instance.update(my_data_dict). I don't see any built-in methods like this, am I missing it or how is this efficiently done?

I have a feeling this is an obvious, RTM kind of question but I just haven't seen it in the docs.


V
Vedprakash Upraity

It's tempting to mess with __dict__, but that won't apply to attributes inherited from a parent class.

You can either iterate over the dict to assign to the object:

for (key, value) in my_data_dict.items():
    setattr(obj, key, value)
obj.save()

Or you can directly modify it from a queryset (making sure your query set only returns the object you're interested in):

FooModel.objects.filter(whatever="anything").update(**my_data_dict)

Thanks @Wilfred. Quite helpful. For others: don't forget to do obj.save() after the for loop
Be ware that the FooModel.objects.filter(whatever="anything").update(**my_data_dict) bypasses the save method. You might end up saving things that should throw exceptions.
D
Daniel Roseman

You could try this:

obj.__dict__.update(my_data_dict)

This won't apply to inherited attributes.
What are the implications of the fact that it won't apply to inherited attributes?
Also, is you are trying to update a ForeignKey, using dict, related values name are field_name_id not field_name, you need to be careful with that.
This didn't persist my record... what might I be missing?
@tomascharad - you would have to call it like obj.__dict__.update(my_data_dict) then obj.save() to persist the data.
l
longhaulblue

It seems like such a natural thing you'd want to do but like you I've not found it in the docs either. The docs do say you should sub-class save() on the model. And that's what I do.

def save(self, **kwargs):
    mfields = iter(self._meta.fields)
    mods = [(f.attname, kwargs[f.attname]) for f in mfields if f.attname in kwargs]
    for fname, fval in mods: setattr(self, fname, fval)
    super(MyModel, self).save()

Thinking of making a decorator for models that adds a new method "dict_save" (so 2 save methods for the model - normal and dict) with the body above the next time I need this. I looked up the code I used to do this and it's the same except that I was doing it outside of save. Had a method update_model_obj_from_dict(model_object, update_dict) with virtually the same body as above and expected that I would choose when to call save() after. I kind of like having the choice. so maybe a method on the model called "set_from_dict" with everything above except the last line and just using save later.
Z
Zulu

I get primary key's name, use it to filter with Queryset.filter() and update with Queryset.update().

fooinstance = ...    
# Find primary key and make a dict for filter
pk_name foomodel._meta.pk.name
filtr = {pk_name: getattr(fooinstance, pk_name)}
# Create a dict attribute to update
updat = {'name': 'foo', 'lastname': 'bar'}
# Apply
foomodel.objects.filter(**filtr).update(**updat)

This allows me to update an instance whatever the primary key.


T
Tony

Update using update()

Discussion.objects.filter(slug=d.slug)
    .update(title=form_data['title'],
            category=get_object_or_404(Category, pk=form_data['category']),
            description=form_data['description'], closed=True)

Welcome to SO. When posting answers please include an explanation of your code and remember to format the text.
Important to note that this does not call the save() method nor the pre_save/post_save signals.
@Maxim is there a way to trigger those signals manually since the save() method was never called?
@enchance you'd have to call the save method on each instance as the update method doesn't call it nor send the pre/post save signals. docs.djangoproject.com/en/3.0/ref/models/querysets/#update