How to update multiple fields of a django model instance? - django

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.

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)

You could try this:
obj.__dict__.update(my_data_dict)

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()

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.

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)

Related

Concise way of getting or creating an object with given field values

Suppose I have:
from django.db import models
class MyContentClass(models.Model):
content = models.TextField()
another_field = models.TextField()
x = MyContentClass(content="Hello, world!", another_field="More Info")
Is there a more concise way to perform the following logic?
existing = MyContentClass.objects.filter(content=x.content, another_field=x.another_field)
if existing:
x = existing[0]
else:
x.save()
# x now points to an object which is saved to the DB,
# either one we've just saved there or one that already existed
# with the same field values we're interested in.
Specifically:
Is there a way to query for both (all) fields without specifying
each one separately?
Is there a better idiom for either getting the old object or saving the new one? Something like get_or_create, but which accepts an object as a parameter?
Assume the code which does the saving is separate from the code which generates the initial MyContentClass instance which we need to compare to. This is typical of a case where you have a function which returns a model object without also saving it.
You could convert x to a dictionary with
x_data = x.__dict__
Then that could be passed into the object's get_or_create method.
MyContentClass.objects.get_or_create(**x_data)
The problem with this is that there are a few fields that will cause this to error out (eg the unique ID, or the _state Django modelstate field). However, if you pop() those out of the dictionary beforehand, then you'd probably be good to go :)
cleaned_dict = remove_unneeded_fields(x_data)
MyContentClass.objects.get_or_create(**cleaned_dict)
def remove_unneeded_fields(x_data):
unneeded_fields = [
'_state',
'id',
# Whatever other fields you don't want the new obj to have
# eg any field marked as 'unique'
]
for field in unneeded_fields:
del x_data[field]
return x_data
EDIT
To avoid issues associated with having to maintain a whitelist/blacklist of fields you, could do something like this:
def remove_unneeded_fields(x_data, MyObjModel):
cleaned_data = {}
for field in MyObjModel._meta.fields:
if not field.unique:
cleaned_data[field.name] = x_data[field.name]
return cleaned_Data
There would probably have to be more validation than simply checking that the field is not unique, but this might offer some flexibility when it comes to minor model field changes.
I would suggest to create a custom manager for those models and add the functions you want to do with the models (like a custom get_or_create function).
https://docs.djangoproject.com/en/1.10/topics/db/managers/#custom-managers
This would be the cleanest way and involves no hacking. :)
You can create specific managers for specific models or create a superclass with functions you want for all models.
If you just want to add a second manager with a different name, beware that it will become the default manager if you don't set the objects manager first (https://docs.djangoproject.com/en/1.10/topics/db/managers/#default-managers)

What is the read/write equivalent of serializers.StringRelatedField?

In Django Rest Framework 3, I want to return the unicode value of a pk relationship, the way you can using a serializer.StringRelatedField, but I need the value to be writable, too. StringRelatedField is read only.
I don't care if the API accepts the pk, or the string value, on the PUT (though accepting the string would be nifty, and would save me grabbing all the pks!). The API just needs to return the unicode string value on the GET.
I'm thinking PrimaryKeyRelatedField might be the way to go, but what does the query look like?
For instance, if the model I want is "Model", and I want Model.name to be serialized, what does this command look like:
name = serializers.PrimaryKeyRelatedField(queryset=Model.objects.get(pk=????))
I'm struggling because I don't know how to get the pk from the serializer object in order to query the related model ...
That's presuming PrimaryKeyRelatedField is what I need, of course. Which may be totally wrong.
Thanks
John
Here are example models as requested, slightly changed for clarity:
class CarModel(models.Model):
name = models.CharField(max_length=100,unique=True)
def __str__(self):
return self.name
class Car(models.Model):
name = models.CharField(max_length=100)
make = models.ForeignKey(CarMake)
car_model = models.ForeignKey(CarModel)
class CarSerializer(serializers.ModelSerializer):
car_model = serializers.StringRelatedField() //like this, but read/write
class Meta:
model = Car
In this example I'm serializing Car, trying to return a string value of CarModel that can be updated with a select dropdown in a form.
If I use different serializers, one for POST that expects the PK and one for everything else that returns the string, the select directive in the form gets very messy.
So, ideally, I just want to be able to POST the string value, and have the API complete the lookup and save the string as a PK.
"I just want to be able to POST the string value, and have the API complete the lookup and save the string as a PK."
That would imply that 'name' should be unique. If it isn't unique then the lookup might return several instances. In the example you currently have 'name' isn't unique, but if it was then you could use...
car_model = serializers.SlugRelatedField(queryset=..., lookup_field='name')
I'm not convinced if that's actually what you want though. The best way to clarify these sorts of questions is typically to forget about the code for a moment, and just focus on a precise description of what you want the input and output representations to look like?...

Access model's foreign key related fields using a string?

A similar question was asked here: Access Django model's fields using a string instead of dot syntax?
I want to be able to access the value of a foreign-key related field in a django model, based on a dynamically generated string. For example, to get a direct field value you would use:
getattr(instance, 'name')
But I want to be able to follow foreign key relations, perhaps like so:
model_getattr(instance, 'name__othername')
This would require another query, and it might be possible to write the function model_getattr by splitting the attribute string on '__'. But I'm curious to know if there's a "correct" way to do this? Is it even possible?
UPDATE:
I managed to write a function that does what I need, but I would still like to know if there's a preferred way. This solution is obviously not as efficient as it could be, but it works for now:
def get_attribute(instance, name):
if hasattr(instance, name):
return getattr(instance, name)
names = name.split('__')
name = names.pop(0)
if len(names) == 0:
return None
if hasattr(instance, name):
value = getattr(instance, name)
return get_attribute(value, '__'.join(names))
return None
I think you need something which is already discussed in below thread
How to get foreign key values with getattr from models

Restrict a model to access only rows with a specific condition?

I want to use a Django model to access a subset of database rows. Working with a number of legacy databases, I'd rather not create views to the database, if possible.
In short, I'd like to tell my model that there's field foo which should always have the value bar. This should span any CRUD operation for the table, so that newly created rows would also have foo=bar. Is there a simple Django way for what I'm trying to achieve?
UPDATE: I want to ensure that this model doesn't write anything to the table where foo != bar. It must be able to read, modify or delete only those rows where foo=bar.
For newly created items you can set the default value in model definition
class MyModel(models.Model):
# a lot of fields
foo = models.CharField(max_length=10, default='bar')
# Set the manager
objects = BarManager()
def save(self, force_insert=False, force_update=False, using=None):
self.foo = 'bar'
super(MyModel, self).save(force_insert, force_update, using)
To achieve that MyModel.objects.all() should return only rows with foo=bar you should implement your custom manager. You can re-define the get_query_set method to add filtering.
class BarManager(models.Manager):
use_for_related_fields = True
def get_query_set(self):
return super(BarManager, self).get_query_set().filter(foo='bar')
Update after #tuomassalo comment
1) The custom manager will affect all calls to MyModel.objects.get(id=42) as this call just proxy a call to .get_query_set().get(id=42). To achieve this you have to set Manager as default manager for model (assign it to objects variable).
To use this manager for related lookups (e.g. another_model_instance.my_model_set.get(id=42)) you need to set use_for_related_fields = True on you BarManager. See Controlling automatic Manager types in the docs.
2) If you want to enforce foo=bar then default value is not enough for you. You can either use pre_save signal or overwrite the save method on your model. Don't forget to call the original save method.
I updated the MyModel example above.

Copy Model Object From a Model To Another In Django

I have to model. I want to copy model object from a model to another:
Model2 is copy of Model1 (this models has too many m2m fields)
Model1:
class Profile(models.Model):
user = models.OneToOneField(User)
car = models.ManyToManyField(Car)
job = models.ManyToManyField(Job)
.
.
This is a survey application. I want to save user's profile when he/she attends the survey (because he can edit profile after survey)
I have created another model to save user profile when he takes survey (Im not sure its the right way)
class SurveyProfile(models.Model):
user = models.OneToOneField(SurveyUser) #this is another model that takes survey users
car = models.ManyToManyField(Car)
job = models.ManyToManyField(Job)
How can I copy user profile from Profile to SurveyProfile.
Thanks in advance
deepcopy etc won't work because the classes/Models are different.
If you're certain that SurveyProfile has the all of the fields present in Profile*, this should work (not tested it):
for field in instance_of_model_a._meta.fields:
if field.primary_key == True:
continue # don't want to clone the PK
setattr(instance_of_model_b, field.name, getattr(instance_of_model_a, field.name))
instance_of_model_b.save()
* (in which case, I suggest you make an abstract ProfileBase class and inherit that as a concrete class for Profile and SurveyProfile, but that doesn't affect what I've put above)
I'm having a tough time understanding what you wrote above, consequently I'm not 100% certain if this will work, but what I think I would do is something like this, if I'm understanding you right:
class Model2Form(ModelForm):
class Meta:
model = models.Model2
and then
f = Model2Form(**m1.__dict__)
if f.is_valid():
f.save()
But I think this looks more like poor database design then anything, without seeing the entire model1 I can't be certain. But, in any event, I'm not sure why you want to do that anyway, when you can simply use inheritance at the model level, or something else to get the same behavior.
Here's the function I've been using, it builds on model_to_dict. Model_to_dict just returns the ids of foreign keys + not their instances, so for those I replace them with the model itself.
def update_model(src, dest):
"""
Update one model with the content of another.
When it comes to Foreign Keys, they need to be
encoded using models and not the IDs as
returned from model_to_dict.
:param src: Source model instance.
:param dest: Destination model instance.
"""
src_dict = model_to_dict(src, exclude="id")
for k, v in src_dict.iteritems():
if isinstance(v, long):
m = getattr(src, k, None)
if isinstance(m, models.Model):
setattr(dest, k, m)
continue
setattr(dest, k, v)
This is how I do it (note: this is in Python3, you might need to change things - get rid of the dictionary comprehension - if you are using python 2):
def copy_instance_kwargs(src, exclude_pk=True, excludes=[]):
"""
Generate a copy of a model using model_to_dict, then make sure
that all the FK references are actually proper FK instances.
Basically, we return a set of kwargs that may be used to create
a new instance of the same model - or copy from one model
to another.
The resulting dictionary may be used to create a new instance, like so:
src_dict = copy_instance_kwargs(my_instance)
ModelClass(**src_dict).save()
:param src: Instance to copy
:param exclude_pk: Exclude the PK of the model, to ensure new records are copies.
:param excludes: A list of fields to exclude (must be a mutable iterable) from the copy. (date_modified, for example)
"""
# Exclude the PK of the model, since we probably want to make a copy.
if exclude_pk:
excludes.append(src._meta.pk.attname)
src_dict = model_to_dict(src, exclude=excludes)
fks={k: getattr(src, k) for k in src_dict.keys() if
isinstance(getattr(src, k, None), models.Model) }
src_dict.update(fks)
return src_dict
I came across something similar but I needed to check also if ForeignKey fields have compatible models. I end up with the following method:
def copy_object(obj, model):
kwargs = dict()
for field in model._meta.fields:
if hasattr(obj, field.name) and not field.primary_key:
if field.remote_field is not None:
obj_field = obj._meta.get_field(field.name)
if obj_field.remote_field != field.remote_field:
continue
kwargs[field.name] = getattr(obj, field.name)
return model(**kwargs)
So, if I'm interpreting your problem correctly, you have an old model (Profile), and you're trying to replace it with the new model SurveyProfile. Given the circumstances, you may want to consider using a database migration tool like South in the long run. For now, you can run a script in the Django shell (manage.py shell):
from yourappname.models import *
for profile in Profile.objects.all():
survey_profile = SurveyProfile()
# Assuming SurveyUser has user = ForeignKey(User)...
survey_profile.user = SurveyUser.objects.get(user=profile.user)
survey_profile.car = profile.car
survey_profile.job = profile.job
survey_profile.save()
Using South
If this project needs to be maintained and updated in the long term, I would highly recommend using a database migration package like South, which will let you modify fields on a Model, and migrate your database painlessly.
For example, you suggest that your original model had too many ManyToManyFields present. With South, you:
Delete the fields from the model.
Auto-generate a schema migration.
Apply the migration.
This allows you to reuse all of your old code without changing your model names or mucking with the database.