I have a model with a custom save function, where I want to perform certain functions based on a condition like this:
class ClassName(models.Model):
def save(self, *args, **kwargs):
reindex = **kwargs.pop("reindex")
super().save(*args, **kwargs)
if reindex:
People.objects.create()
Now inside a task I want to call the following:
kwargs = { "reindex": False}
ClassName.objects.get_or_create(**kwargs)
When it does a create, it obviously runs the save function, but it's giving me an error saying reindex is not a field.
I have been researching it for a while now and can't figure out what to do.
Maybe someone can point me in the right direction.
I just want to pass in an argument into the get_or_create, so that I can conditionally perform a certain function in the save method.
When you do
kwargs = { "reindex": False}
ClassName.objects.get_or_create(**kwargs)
it is actually equivalent to
ClassName.objects.get_or_create(reindex=False)
Thus, since reindex appears not to be a field defined in the model ClassName, you get an error.
BTW, beyond things which appear erroneous, e.g. reindex = **kwargs.pop("reindex"), you should define reindex as one of the fields of your model. But I admit that I answer blindly, because to me, your class definition cannot work like so. If one assumes that reindex is an integer field, you could do
class ClassName(models.Model):
reindex = models.IntegerField(null=True)
def save(self, *args, **kwargs):
super(ClassName, self).save(*args, **kwargs)
if "reindex" in kwargs:
People.objects.create()
Related
I'm new to Django. I understand what are the usage of *args and **kwargs. And also know how to use them in method overriding.
But, I don't understand what purpose they serve while overriding the save() method in a model class.
My observation is that no number of arguments, either non-keyworded or keyworded, were assigned to them anywhere. Still why do I must use them and how.
Have this example:
class DemoModel(models.Model):
title = models.CharField(max_length = 200)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(DemoModel, self).save(*args, **kwargs)
Please explain.
From Django model documentation:
It’s also important that you pass through the arguments that can be
passed to the model method – that’s what the *args, **kwargs bit does.
Django will, from time to time, extend the capabilities of built-in
model methods, adding new arguments. If you use *args, **kwargs in
your method definitions, you are guaranteed that your code will
automatically support those arguments when they are added.
Very late but,
You should add return to the save method.
return super(DemoModel, self).save(*args, **kwargs)
Secondly, the kwargs which means keyword arguments are also URL parameters that are passed to the views like kwargs={"pk": self.object.id} when you save the method and it needs to redirect to a detail view for example, it needs the id of the just created object. The magic happens already in Django views, but you can pass extra parameters if you want.
I guess what I need to do it overwrite my model's save method but do correct me if I'm wrong/have better suggestion.
This is what my model looks like:
class MergingModel(models.Model):
some_field = models.TextField()
And this is the unit test that I want to pass:
class MergingModelTests(TestCase):
def test_duplicates_are_overwriten(self):
MergingModel.create(id=1)
MergingModel.create(id=1, some_field="abc")
self.assertEquals(MergingModel.objects.count(),1)
self.assetEquals(MergingModel.objects.get(id=1).some_field,"abc")
I tried overwriting save method to check if record with id=x exists but that raised recursion errors, my code for save method looked like:
def save(self, *args, **kwargs):
if MergingModel.objects.filter(id__exact=self.id):
original = MergingModel.objects.get(id=self.id)
original.some_field = self.some_field
original.save()
else:
super().save( *args, **kwargs)
Then I tried overwriting create but I get error:
Key (id)=(ID1) already exists
So I'm not really sure what to do anymore.
I have a model with overridden __init__ method like this:
class MyModel(models.Model):
...
def __init__(self, *args, **kwargs):
if not kwargs.get('skip', False):
do_something()
super().__init__(*args, **kwargs)
How can I pass skip argument to __init__, when I iter the queryset:
data = [obj for obj in MyModel.objects.all()]
I would like to implement this via method in custom manager, for use this something like this: queryset.with_skip()
I see that you don't remove the the argument skip from kwargs before passing it to super().__init__. That means that "skip" is a name of field, otherwise you got exception TypeError("'skip' is an invalid keyword argument for this function").
If you really need do_something() when the object is created before using so indispensably that nobody should forget to avoid all unsupported ways (??) then custom managers etc. are not enough.
Your problem is that models.Model.__init__(...) supports both *args and **kwargs arguments so perfectly that they should be interchangeable. You broke it and if the "skip" is passed by the complete tuple of positional arguments, you ignore it. That is if the object is created from the database. Read the docs Customizing model loading:
... If all of the model’s fields are present, then values are guaranteed to be in the order __init__() expects them. That is, the instance can be created by cls(*values)...
.
| #classmethod
| def from_db(cls, db, field_names, values):
| ...
| instance = cls(*values)
| ...
An easy way to fix it is to call do_something() after super().__init__ and read self.skip instead of implement parsing both kwargs and args.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.skip:
do_something()
A problem could be the signal "post_init" that is sent at the end of super().__init__ if you need it.
The last possibility is to support *args (hacky, but still uses documented names someway):
def __init__(self, *args, **kwargs):
if kwargs:
skip = kwargs.get('skip', False)
else:
# check "val is True" in order to skip if val is DEFERRED
skip = any(field.name == 'skip' and val is True
for val, field in zip(args, self._meta.concrete_fields)
)
if not skip:
do_something()
super().__init__(*args, **kwargs)
EDIT: Maybe you you don't need what you wanted and a Proxy model that can do something extra sometimes over a basic model on the same data in the same database table is the right solution. ("Skip" doesn't look like a name describing the object data, but like a name describing a mode of object creation. It is easier to test and maintain a subclass than a mysterious switch inside.)
I have a model that looks something like:
class DooDad(models.Model):
doo_dad_dogue = models.BooleanField(default=True)
Trouble is, that default needs to be manipulated by... stuff that is irrelevant to this question. If I were creating the form that creates the object, the solution would be trivial. I'm still using the django default form for creating these things, though, and I'd rather keep it that way.
I tried the obvious:
class DooDad(models.Model):
doo_dad_dogue = models.BooleanField(default=True)
def __init__(self, *args, **kwargs):
super(DooDad, self).__init__(*args, **kwargs)
self.doo_dad_dogue = False
...which I suspect would have terrible side effects, but was worth experimenting with. The form still comes up with the box checked.
EDIT: I should have mentioned that this is Django 1.9
If it is not possible to continue using the default model creation form, is there anything unusual that I need to do to to make a ModelForm that only impacts CREATE, and not EDIT?
I do not think using the __init__in model is a good practice. However, if you want to try it is important to know that your code is not correct one the field doo_dad_dogue is a descriptor. The correct way to access it is
using self.fields['doo_dad_dogue'] = False.
Using a form is the correct way to do that. You can override the default value in the Form by using the init method:
def __init__(self, *args, **kwargs):
super(<YOUR_FORM>, self).__init__(*args, **kwargs)
if args is not None and len(args) and args[0] is not None:
<MANIPULATE HERE>
Hope that helps.
I have a model with a customized save() method that creates intermediate models if the conditions match:
class Person(models.Model):
integervalue = models.PositiveIntegerField(...)
some_field = models.CharField(...)
related_objects = models.ManyToManyField('OtherModel', through='IntermediaryModel')
...
def save(self, *args, **kwargs):
if self.pk is None: # if a new object is being created - then
super(Person, self).save(*args, **kwargs) # save instance first to obtain PK for later
if self.some_field == 'Foo':
for otherModelInstance in OtherModel.objects.all(): # creates instances of intermediate model objects for all OtherModels
new_Intermediary_Model_instance = IntermediaryModel.objects.create(person = self, other = otherModelInstance)
super(Person, self).save(*args, **kwargs) #should be called upon exiting the cycle
However, if editing an existing Person both through shell and through admin interface - if I alter integervalue of some existing Person - the changes are not saved. As if for some reason last super(...).save() is not called.
However, if I were to add else block to the outer if, like:
if self.pk is None:
...
else:
super(Person, self).save(*args, **kwargs)
the save() would work as expected for existing objects - changed integervalue is saved in database.
Am I missing something, or this the correct behavior? Is "self.pk is None" indeed a valid indicator that object is just being created in Django?
P.S. I am currently rewriting this into signals, though this behavior still puzzles me.
If your pk is None, super's save() is called twice, which I think is not you expect. Try these changes:
class Person(models.Model):
def save(self, *args, **kwargs):
is_created = True if not self.pk else False
super(Person, self).save(*args, **kwargs)
if is_created and self.some_field == 'Foo':
for otherModelInstance in OtherModel.objects.all():
new_Intermediary_Model_instance = IntermediaryModel.objects.create(person = self, other = otherModelInstance)
It's not such a good idea to override save() method. Django is doing a lot of stuff behind the scene to make sure that model objects are saved as they expected. If you do it in incorrectly it would yield bizarre behavior and hard to debug.
Please check django signals, it's convenient way to access your model object information and status. They provide useful parameters like instance, created and updated_fields to fit specially your need to check the object.
Thanks everyone for your answers - after careful examination I may safely conclude that I tripped over my own two feet.
After careful examination and even a trip with pdb, I found that the original code had mixed indentation - \t instead of \s{4} before the last super().save().