I am binding a post_save method to a model in django, like the following code:
def save_mymodel(sender,instance,*args,**kwargs):
print 'save called'
for parameter in instance.parameters.all():
print parameter.name
post_save.connect(save_mymodel, sender=MyModel)
Here are my models:
def MyModel(models.Model):
parameters = models.ManyToManyField(Parameter)
def Parameter(models.Model):
name = models.CharField(max_length=100)
When I try create a MyModel from django admin with any number of parameters, I only get save called as output. If I save the same MyModel object again from the admin, all the parameters are printed. What is the difference between calling save on create and not on create? How can I make sure I get all the attributes of a model using post_save on its creation?
ManyToMany fields do not participate in model save() method, so post_save would not recognize any m2m changes. To detect m2m change, use m2m_changed signal.
django doc about m2m_changed.
Related
I have a signal that listens to a model whose relation to another model is like this
class Model1(models.Model):
field1 = models.CharField(max_length=4)
class ChildToModel1(models.Model):
model_1 = models.ForeinKey(Model1, related_name='model_1', on_delete=models.CASCADE)
Now I have an inlineformset_factory for ChildToModel1 according to django calling save for ChildToModel1 inlineformset_factory will save multiple instance of ChildToModel1 like it's a forloop. If I have, for example; 3 formset is there a way to check the total of the objects after total save and not on each save, I am accessing these on the model post_save signal.
printing ChildToModel1 on post_save signal returns something like this
<QuerySet [obj]>
<QuerySet [obj][obj]>
<QuerySet [obj][obj][obj]>
What I really want is:
<QuerySet [obj][obj][obj]>
You can trigger other signal manually at the end.
First define new signal: https://docs.djangoproject.com/en/4.0/topics/signals/#defining-signals
import django.dispatch
post_save_after_last = django.dispatch.Signal()
and then import it and trigger it
from somewhere import post_save_after_last
last_instance = ChildToModel1(field='qwerty')
post_save_after_last.send(sender=ChildToModel1, instance=instance)
You can read more here: https://docs.djangoproject.com/en/4.0/topics/signals/#sending-signals
I have a model 'B' that is linked to another model 'A' as an inline model, for use in my admin site. Now, whenever I delete an object of model 'B' associated with the corresponding object of model 'A' (via the admin site), I want to perform some more tasks at the backend. I was able to override the save function using a formset and then overriding the save_existing and save_new methods. How do I go about overriding the delete method for the inline admin model?
I was able to get around by overriding the save() method for my model in the models.py itself.
Use pre_delete or post_delete signals to execute code before/after model is deleted:
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from myapp.models import MyModel
#receiver(pre_delete, sender=MyModel)
def my_handler(sender, **kwargs):
...
I have a model that contains multiple many to many fields:
class Author(models.Model):
name = models.CharField(max_length=30)
class Topic(models.Model):
description = models.CharField(max_length=30)
class Article(models.Model):
authors = models.ManyToManyField(Author, related_name='articles')
topics = models.ManyToManyField(Topic, related_name='articles')
I need something pretty simple:
A method to be executed after the article is saved where I can access both authors and topics of that instance.
My first attempt has been with a post_save signal, but the signal is fired when the model itself is saved, not after its relationships are saved, which come from a through model obviously.
After some online reading I realized that I probably need to create my own signal and connect to it. The problem is that I have no idea what to overwrite and where to fire that signal.
Since I need this on multiple models, I thought I could create some M2MPostSaveModel class and have my models inherit it so I can just catch the signals..
But where does Django sends the signals? How can I overwrite it? I honestly have no idea and I had no luck searching in the docs, so apologies if it was there already and I didn't see it.
I finally did it, here's how:
I created a new signal
import django.dispatch
m2m_post_save = django.dispatch.Signal(providing_args=["instance"])
Then I created a subclass of the ModelAdmin class that fires it after it finishes saving the related elements:
class M2MPostSaveModelAdmin(ModelAdmin):
def save_related(self, request, form, formsets, change):
super(M2MPostSaveModelAdmin, self).save_related(request, form, formsets, change)
m2m_post_save.send(sender=self.__class__, instance=form.instance)
Now all I have to do is to subclass my ModelAdmin class with my M2MPostSaveModelAdmin class and hook up the signal to my method.
def doo_something_with_updated_instance(instance, **kwargs):
# Here your instance has all the m2m relationships updated
m2m_post_save.connect(doo_something_with_updated_instance sender=YourModelAdminClass)
I used m2m_changed for separate processing.
def topics_changed(sender, instance, **kwargs):
# Do something
m2m_changed.connect(topics_changed, sender=Article.topics.through)
so do with authors also
Apparently Django's ModelAdmin/ModelForm doesn't allow you to use save_m2m() if there's an intermediate through table for a ManyToManyField.
models.py:
from django.db import models
def make_uuid():
import uuid
return uuid.uuid4().hex
class MyModel(models.Model):
id = models.CharField(default=make_uuid, max_length=32, primary_key=True)
title = models.CharField(max_length=32)
many = models.ManyToManyField("RelatedModel", through="RelatedToMyModel")
def save(self, *args, **kwargs):
if not self.id:
self.id = make_uuid()
super(GuidPk, self).save(*args, **kwargs)
class RelatedModel(models.Model):
field = models.CharField(max_length=32)
class RelatedToMyModel(models.Model):
my_model = models.ForeignKey(MyModel)
related_model = models.ForeignKey(RelatedModel)
additional_field = models.CharField(max_length=32)
admin.py:
from django import forms
from django.contrib import admin
from .models import MyModel
class RelatedToMyModelInline(admin.TabularInline):
model = MyModel.many.through
class MyModelAdminForm(forms.ModelForm):
class Meta:
model = MyModel
class MyModelAdmin(admin.ModelAdmin):
form = MyModelAdminForm
inlines = (RelatedToMyModelInline, )
admin.site.register(MyModel, MyModelAdmin)
If I save MyModel first and then add a new related through model via the inline it works fine, but if I try to set the inline while also adding data for a new MyModel, I get the Django Admin error "Please correct the error below." with nothing highlighted below.
How can I have it save MyModel and then save the inline intermediary models after? Clearly Django can save the through model once it has saved MyModel - so I'm just looking for a hook into that. I tried overriding the form's save() method by calling save_m2m() after calling instance.save(), but apparently that doesn't work for M2Ms with a through table.
I'm using Django 1.2, but this is still an issue in 1.3.
UPDATE: Well, I made a test app like above to isolate the problem, and it appears that it works as expected, correctly saving the M2M intermediary object after saving the MyModel object... as long as I let Django automatically create the MyModel.id field when running python manage.py syncdb - once I added the GUID id field, it no longer works.
This smells more and more like a Django bug.
In your MyModelAdmin you might try overriding the save_formset method. This way you can choose the order in which you save.
I have a model, Foo. It has several database properties, and several properties that are calculated based on a combination of factors. I would like to present these calculated properties to the user as if they were database properties. (The backing factors would be changed to reflect user input.) Is there a way to do this with the Django admin interface?
I would suggest you subclass a modelform for Foo (FooAdminForm) to add your own fields not backed by the database. Your custom validation can reside in the clean_* methods of ModelForm.
Inside the save_model method of FooAdmin you get the request, an instance of Foo and the form data, so you could do all processing of the data before/after saving the instance.
Here is an example for a model with a custom form registered with django admin:
from django import forms
from django.db import models
from django.contrib import admin
class Foo(models.Model):
name = models.CharField(max_length=30)
class FooAdminForm(forms.ModelForm):
# custom field not backed by database
calculated = forms.IntegerField()
class Meta:
model = Foo
class FooAdmin(admin.ModelAdmin):
# use the custom form instead of a generic modelform
form = FooAdminForm
# your own processing
def save_model(self, request, obj, form, change):
# for example:
obj.name = 'Foo #%d' % form.cleaned_data['calculated']
obj.save()
admin.site.register(Foo, FooAdmin)
Providing initial values for custom fields based on instance data
(I'm not sure if this is the best solution, but it should work.)
When a modelform for a existing model instance in the database is constructed, it gets passed this instance. So in FooAdminForm's __init__ one can change the fields attributes based on instance data.
def __init__(self, *args, **kwargs):
super(FooAdminForm, self).__init__(*args, **kwargs)
# only change attributes if an instance is passed
instance = kwargs.get('instance')
if instance:
self.fields['calculated'].initial = (instance.bar == 42)
It's easy enough to get arbitrary data to show up in change list or make a field show up in the form: list_display arbitrarily takes either actual model properties, or methods defined on the model or the modeladmin, and you can subclass forms.ModelForm to add any field type you'd like to the change form.
What's far more difficult/impossible is combining the two, i.e. having an arbitrary piece of data on the change list that you can edit in-place by specifying list_editable. Django seems to only accept a true model property that corresponds to a database field. (even using #property on the method in the model definition is not enough).
Has anyone found a way to edit a field not actually present on the model right from the change list page?
In the edit form, put the property name into readonly_fields (1.2 upwards only).
In the changelist, put it into list_display.
You can use the #property decorator in your model (Python >= 2.4):
class Product(models.Model):
#property
def ranking(self):
return 1
"ranking" can then be used in list_display:
class ProductAdmin(admin.ModelAdmin):
list_display = ('ranking', 'asin', 'title')