I'm trying to update an object field in django. Usually I would do something like this:
# MODEL ---
class MyObj(models.model):
name: models.CharField(max_length=10)
surname: models.CharField(max_length=10)
# VIEW ---
# [...]
myObj = MyObj.objects.get(pk=1)
myObj.name = 'John'
myObj.save()
The problem is that the field to modify (in the example above: "name") is not known and passed as an argument to the post request. So I would have something like this:
# VIEW ---
# [...]
field = self.request.query_params['field_to_modify']
myObj = MyObj.objects.get(pk=1)
myObj[field] = 'John'
myObj.save()
now this triggers the error:
myObj[field] = 'John'
TypeError: 'MyObj' object does not support item assignment
What is the correct way to update an "unknown" field of a django object?
UPDATE
Thank you for your answers so far! OK so the way to go is apparently using setattr (as per the answers). Now the problem is that it does not allow me to save the modified object without checking if it is valid.
So I tried using the Serializer to check the object validity but is not working:
field = self.request.query_params['field_to_modify']
myObj = MyObj.objects.get(pk=1)
setattr(myObj, field, 'John')
serial = MyObjSerializer(myObj)
serial.is_valid(raise_exception=True)
serial.save()
error:
AssertionError: Cannot call `.is_valid()` as no `data=` keyword argument was passed when instantiating the serializer instance.
You're looking for setattr to set an attribute by name.
field = self.request.query_params['field_to_modify']
# TODO: add validation for `field`
myObj = MyObj.objects.get(pk=1)
setattr(myObj, field, 'John')
myObj.save(update_fields=[field]) # no need to update anything but the single field
Of course, this won't let you automatically attach any arbitrary data to models and expect it to be saved; if you need something like that, maybe use a JSON field.
EDIT:
For using a Django REST Framework serializer (which, cough, wasn't part of the original question or tags, but could be inferred from query_params), one could do
field = self.request.query_params['field_to_modify']
myObj = MyObj.objects.get(pk=1)
serial = MyObjSerializer(instance=myObj, data={field: 'John'})
serial.is_valid(raise_exception=True)
serial.save()
Yeah it looks like Django model instances do not support that operation. It looks like you can use setattr though from looking at this previous question / answer. Update model instance with dynamic field names
Related
I have and Employee model and an EmployeeType model, with Employee having an attribute called Employee.employee_type which is of type EmployeeType.
Currently on the creation of a client, we run something to create "default" values for the EmployeeType. Right now, that logic is in a method within the module that handles a new client being created... but I was thinking a better place for this would be to be with the EmployeeType model.
My question is - would it be appropriate to make a custom Manager attribute on the EmployeeType that does the creation and/or fetching of these default types? See the following for what I believe I am trying to accomplish:
class DefaultValueEmployeeTypeManager(models.Manager):
def get_or_create_default_values(self):
first_type = self.model.objects.get_or_create(name='First Type')
second_type = self.model.objects.get_or_create(name='Second Type')
third_type = self.model.objects.get_or_create(name='Third Type')
return (first_type, second_type, third_type)
class Employee(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
defaults = DefaultValueEmployeeTypeManager()
# Code in another file, handling the setup of a new client
from models import EmployeeType
def create_new_client(client):
# make sure the default values are there
EmployeeType.defaults.get_or_create_default_values()
My question is whether or not this is acceptable/expected behavior for a Manager object to handle? Or should this just be some sort of #classmethod (or similar) on the EmployeeType model?
You can do that, but I wouldn't rename the manager to defaults because now objects is undefined and all queries on EmployeeType need to use defaults, e.g. EmployeeType.defaults.all() which doesn't really make sense.
You just want to add an extra method, like described here.
Just name your manager objects = EmployeeTypeManager(), call your manager EmployeeTypeManager (it's still the default manager) and your method is just an extra manager method.
Note that get_or_create returns a two-tuple of the object and whether or not it was created. So in your code first_type is (<the object>, False) if the type already exists.
Note also you put this under the Employee model, but it's of course an EmployeeType model manager.
I'm trying to create a couple django models with a one to one relation. However I'm trying to get it so the related one to one model is automatically created. If I have something simple like this:
class MyObject(models.Model):
data = models.OneToOneField('MyData', related_name='my_object')
class MyData(models.Model):
info = models.TextField(null=True)
If I create a MyObject and access MyObject.data it will return None. I was hoping there was a way I can have it return a MyData object (just default reference).
I'd like MyObject to automatically have a related MyData object. Is there a way for me to do this or do I need to check every time to see if there's a related MyData object?
Have you seen the official doc?
d = MyData(info='whatever')
o = MyObject(data=d)
How can it be automatic if info text field has to be filled in?
after seeing your edit:
you can probably set my data to be null
o = MyObject(data=Mydata(info=None))
of course, your Mydata should now be able to accept None as their type.
Hello,
I have bound a ModelForm to one of my model that contains a ForeignKey to another model everything driven by a CreateView. What I want to achieve is to create the model object corresponding to the foreign key if it doesn't exist before the form is overall validated and the final object created in database.
Below the models I use:
class UmsAlerting(models.Model):
alert_id = models.IntegerField(primary_key=True, editable=False)
appli = models.ForeignKey('UmsApplication')
env = models.ForeignKey('UmsEnvironment')
contact = models.ForeignKey('UmsContacts')
custom_rule = models.ForeignKey('UmsCustomRules', null=True, blank=True)
class UmsApplication(models.Model):
appli_id = models.IntegerField(primary_key=True)
trigram_ums = models.CharField(max_length=4L)
class UmsContacts(models.Model):
contact_id = models.IntegerField(primary_key=True)
mail_addr = models.CharField(max_length=100L)
class UmsEnvironment(models.Model):
env_id = models.IntegerField(primary_key=True)
env_name = models.CharField(max_length=5L)
The model bound to the form is UmsAlerting. The model object I want to create if it doesn't exist is UmsContacts. I managed to use the field's clean method in my ModelForm of the contact field and use the get_or_create method like below:
def clean_contact(self):
data = self.cleaned_data['contact']
c, _ = UmsContacts.objects.get_or_create(mail_addr=data)
return c
It perfectly works when the contact is already in the database but when it needs to be created my form return a ValidationError on the contact field saying "This field cannot be null". If I submit the same form a second time without changing anything the UmsAlerting object is well created with no validation error.
My guess is that, for a reason I don't get, when get_or_create is used to create a UmsContacts object it cannot be used to create the new UmsAlerting object. So in clean_contact method the get is working and returns the UmsContacts object but the create part doesn't. It'd be like the UmsContacts object is saved when the whole form is validated but not before as I'd want it to.
Anyone could help me find out what is the problem ? Is using the clean method not the best idea ? Is there another strategy to use to take around this problem ?
Thanks in advance for your help.
It's probably because the object you are creating expects value for contact_id. If you use contact_id field for just setting object id -then you do not have to create it at all. Django takes care of Id's automatically.
Also. field clean method should return cleaned data not object. That creates whole lot more problems on its own.
I am write one easy program using GAE and python 2.7, but I met some problem while stored data into db. My code is below:
class MemberInfo(db.Model):
firstName = db.StringProperty(required=True)
class RegisterPageButtonDown(webapp2.RequestHandler):
def post(self):
memberInfo = MemberInfo()
memberInfo.firstName = self.request.get('firstName')
memberInfo.put()
The error raise in "memberInfo = MemberInfo()", it said "Property firstName is required". I am sure I put data in html form and the method is post, too.
I've been stuck in this problem for whole night, thanks for your reply.
You've set the firstName property to required, so when you instantiate an object you must provide that property with a value, e.g.:
memberInfo = MemberInfo(firstName = self.request.get('firstName'))
Alternatively, you can make firstName not required in your model.
The error is coming from the first line of the function, before you even get the value from the request. This is because you need to pass in that value when you instantiate the object.
firstName = self.request.get('firstName')
memberInfo = MemberInfo(firstName=firstName)
(Also note that normal naming conventions for Python mean that variables and properties are lower_case_with_underscore, not camelCase.)
I have this manytomany field in my model that I have overridden with a CharField that receives a csv list of the second models name attribute.
class PostForm(ModelForm):
tests = CharField(label="tests")
class Meta:
model = Post
fields = ('title','body')
def clean_tests(self):
# Here I clean, create or retrieve and return a list of Test objects.
Now, saving and validating is alright with this code, everything works, my problem comes when I create the PostForm with an existing instance, like PostForm(instance=current_post).
The CharField should contain a csv list but it contains nothing, obviously this happens because there is no conversion happening from Test object list to test name list, the problem is I do not know where to put that code, I see no method I could override to get this done, i've looked into initial data and default properties of fields.
I'm not sure if there's a method you could override to do this -- from a look at the BaseModelForm constructor though, it looks perfectly okay to specify both the instance and initial keyword arguments together -- the instance is converted into a dict (subject to the fields and exclude options in Meta), and that dict's update method is called with initial. Something like this should work:
# build your csv list somehow (just speculation here)
tests = ','.join(test.name for test in current_post.tests.all())
form = PostForm(instance=current_post, initial={'tests': tests})