I want to update fields in django admin site following the modification by the user of a given foreignkey field. I seek a way to trigger the event straight after foreignkey modification; not before/after save.
I looked for the creation of a custom signal through sender/receiver. Also I tried to find a similar signal as the m2m_changed. Perhaps one of these path could be the solution.
class Variety(models.Model):
specific_name = models.ForeignKey(CatalogList, on_delete=models.CASCADE)
def update_other_field(self):
#DO SOMETHING EACH TIME specific_name IS MODIFIED
Is there a way to trigger an event directly after a foreignkey is modified?
You can override save method of "parent" model :
class CatalogList(models.Model):
def save(self, *args, **kwargs):
super().save(*args, **kwargs) # Call normal save method
# Then do what you want with variety_set reverse relation
Related
I want to override Django Queryset Update method , to log the model changes in another table.I have override the method , but not able to find the id's of the rows which are going to get update.I am getting the fields which are getting changed from kwargs
I'm using Django v1.9.5.
I went through the docs of django-simple-history and django-reversion , but they don't log changes on update method.
class PollQuerySet(QuerySet):
def update(self, *args, **kwargs):
# save data into other table whose schema is
#(model_name,field_name,model_pk_id,old_value,new_value)
super().update(*args, **kwargs)
class ModelWithCustomManager(models.Model):
objects = PollQuerySet.as_manager()
class Meta:
abstract = True
Instead of overriding the update method, your may want to look in to signals. On pre-save and post-save you can grab data from the model and save it into a log table.
I have the following models:
class Item(models.Model):
# fields
# ...
class Collection(models.Model):
items = models.ManyToManyField(Item, related_name="collections")
# other fields
# ...
Now I want two things:
I want to control if an Item can be added to a Collection.
I want the Collection to update some of its fields if an Item was added or removed.
For the second issue I know that there is the django.db.models.signals.m2m_changed which I can use to hook into changes of the relation. Is it allowed/ok to change the Collection within the signal callback? Can I use the signal also for "aborting" the insertion for issue 1?
I think the best way to approach both of your desired behaviors is not with signals, but rather with an overridden save() and delete() method on the through table which you would define explicitly using the argument through see: https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ManyToManyField.through. and this: https://docs.djangoproject.com/en/dev/topics/db/models/#overriding-predefined-model-methods
Something like this:
# -*- coding: utf-8 -*-
from django.db import models
class Item(models.Model):
# fields
# ...
class Collection(models.Model):
items = models.ManyToManyField(Item, related_name="collections", through="CollectionItem")
# other fields
# ...
class CollectionItem(models.Model):
collection = models.ForeignKey(Collection)
item = models.ForeignKey(Item)
def save(self, *args, **kwargs):
# Only allow this relationship to be created on some_condition
# Part 1 of your question.
if some_condition:
super(CollectionItem, self).save(*args, **kwargs)
# Update some fields on Collection when this
# relationship is created
# Part 2 of your question (1/2)
self.Collection.updateSomeFields()
def delete(self, *args, **kwargs):
collection = self.collection
super(CollectionItem, self).delete(*args, **kwargs)
# Update some fields on Collection when this relationship
# is destroyed.
# Part 2 of your question (2/2)
collection.updateSomeFields()
Incidentally, you'll find that adding a relationship will cause a save-signal on this through model.
And, regarding signals, once you have the through table in place, you'd be able to listen for pre_save and/or post_save signals, but neither of them will allow you to directly veto the creation of the relationship.
If one or both of your models are supplied by a 3rd party and you really cannot create the through table, then, yes, the signal route may be the only way to go.
https://docs.djangoproject.com/en/dev/ref/signals/#m2m-changed
In which case, you could listen for the m2m_changed event and trigger updates to your collection objects (part 2 of your question) and retroactively delete inappropriately created relationships (part 1 of your question). However, as this latter bit is a fugly kludgy, I'd stick with the explicit through table if you can.
The pre_save signal is called before saving an instance. But you are not able to abort the save operation from there. A better solution would be to add a new method to your Collection model, which is responsible for checking if an Item can be added:
class Collection(models.Model):
items = models.ManyToManyField(Item, related_name="collections")
...
def add_item(self, item):
if check_if_item_can_be_added(item):
items.add(item)
self.save()
def check_if_item_can_be_added(self, item):
# do your checks here
When adding an instance to a m2m field, the save method does not get called. You are right, the m2m_changed signal is the way to go. You can safely update the collection instance in there.
When is the appropriate time to do validation on related fields in a model?
For example if I have a class Video that has a ManyToMany relationship with a class Playlist, when the Video is changed to 'private', it should be removed from all Playlists.
Doing this in the model's clean() method seems dangerous - since the model might fail validation and not save, but the Playlist references will have been deleted.
Is doing it in a post_save or pre_save signal the right way to go?
You have two choices:
First one is using a post_save signal that does the job. Not a pre_save, because saving can fail and I guess you only want to do that if the save worked right.
Other option is overriding model's save() method like:
def save(self, *args, **kwargs):
super(MyModel, self).save(*args, **kwargs)
# do stuff for removing whatever you want to remove
My personal choice is the first one because you deal with different models. If you only need to deal with the current one, I'll do the second one. But it's just a personal thought.
I have a model which is overriding save() to slugify a field:
class MyModel(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(MyModel, self).save(*args, **kwargs)
When I run load data to load a fixture, this save() does not appear to be called because the slug field is empty in the database. Am I missing something?
I can get it to work by a pre_save hook signal, but this is a bit of a hack and it would be nice to get save() working.
def mymodel_pre_save(sender, **kwargs):
instance = kwargs['instance']
instance.slug = slugify(instance.name)
pre_save.connect(mymodel_pre_save, sender=MyModel)
Thanks in advance.
No you're not. save() is NOT called by loaddata, by design (its way more resource intensive, I suppose). Sorry.
EDIT: According to the docs, pre-save is not called either (even though apparently it is?).
Data is saved to the database as-is, according to https://docs.djangoproject.com/en/dev/ref/django-admin/#what-s-a-fixture
I'm doing something similar now - I need a second model to have a parallel entry for each of the first model in the fixture. The second model can be enabled/disabled, and has to retain that value across loaddata calls. Unfortunately, having a field with a default value (and leaving that field out of the fixture) doesn't seem to work - it gets reset to the default value when the fixture is loaded (The two models could have been combined otherwise).
So I'm on Django 1.4, and this is what I've found so far:
You're correct that save() is not called. There's a special DeserializedObject that does the insertion, by calling save_base() on the Model class - overriding save_base() on your model won't do anything since it's bypassed anyway.
#Dave is also correct: the current docs still say the pre-save signal is not called, but it is. It's behind a condition: if origin and not meta.auto_created
origin is the class for the model being saved, so I don't see why it would ever be falsy.
meta.auto_created has been False so far with everything I've tried, so I'm not yet sure what it's for. Looking at the Options object, it seems to have something to do with abstract models.
So yes, the pre_save signal is indeed being sent.
Further down, there's a post_save signal behind the same condition that is also being sent.
Using the post_save signal works. My models are more complex, including a ManyToMany on the "Enabled" model, but basically I'm using it like this:
from django.db.models.signals import post_save
class Info(models.Model):
name = models.TextField()
class Enabled(models.Model):
info = models.ForeignKey(Info)
def create_enabled(sender, instance, *args, **kwards):
if Info == sender:
Enabled.objects.get_or_create(id=instance.id, info=instance)
post_save.connect(create_enabled)
And of course, initial_data.json only defines instances of Info.
Given a model with ForeignKeyField (FKF) or ManyToManyField (MTMF) fields with a foreignkey to 'self' how can I prevent self (recursive) selection within the Django Admin (admin).
In short, it should be possible to prevent self (recursive) selection of a model instance in the admin. This applies when editing existing instances of a model, not creating new instances.
For example, take the following model for an article in a news app;
class Article(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField()
related_articles = models.ManyToManyField('self')
If there are 3 Article instances (title: a1-3), when editing an existing Article instance via the admin the related_articles field is represented by default by a html (multiple)select box which provides a list of ALL articles (Article.objects.all()). The user should only see and be able to select Article instances other than itself, e.g. When editing Article a1, related_articles available to select = a2, a3.
I can currently see 3 potential to ways to do this, in order of decreasing preference;
Provide a way to set the queryset providing available choices in the admin form field for the related_articles (via an exclude query filter, e.g. Article.objects.filter(~Q(id__iexact=self.id)) to exclude the current instance being edited from the list of related_articles a user can see and select from. Creation/setting of the queryset to use could occur within the constructor (__init__) of a custom Article ModelForm, or, via some kind of dynamic limit_choices_to Model option. This would require a way to grab the instance being edited to use for filtering.
Override the save_model function of the Article Model or ModelAdmin class to check for and remove itself from the related_articles before saving the instance. This still means that admin users can see and select all articles including the instance being edited (for existing articles).
Filter out self references when required for use outside the admin, e.g. templates.
The ideal solution (1) is currently possible to do via custom model forms outside of the admin as it's possible to pass in a filtered queryset variable for the instance being edited to the model form constructor. Question is, can you get at the Article instance, i.e. 'self' being edited the admin before the form is created to do the same thing.
It could be I am going about this the wrong way, but if your allowed to define a FKF / MTMF to the same model then there should be a way to have the admin - do the right thing - and prevent a user from selecting itself by excluding it in the list of available choices.
Note: Solution 2 and 3 are possible to do now and are provided to try and avoid getting these as answers, ideally i'd like to get an answer to solution 1.
Carl is correct, here's a cut and paste code sample that would go in admin.py
I find navigating the Django relationships can be tricky if you don't have a solid grasp, and a living example can be worth 1000 time more than a "go read this" (not that you don't need to understand what is happening).
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['myManyToManyField'].queryset = MyModel.objects.exclude(
id__exact=self.instance.id)
You can use a custom ModelForm in the admin (by setting the "form" attribute of your ModelAdmin subclass). So you do it the same way in the admin as you would anywhere else.
You can also override the get_form method of the ModelAdmin like so:
def get_form(self, request, obj=None, **kwargs):
"""
Modify the fields in the form that are self-referential by
removing self instance from queryset
"""
form = super().get_form(request, obj=None, **kwargs)
# obj won't exist yet for create page
if obj:
# Finds fieldnames of related fields whose model is self
rmself_fields = [f.name for f in self.model._meta.get_fields() if (
f.concrete and f.is_relation and f.related_model is self.model)]
for fieldname in rmself_fields:
form.base_fields[fieldname]._queryset =
form.base_fields[fieldname]._queryset.exclude(id=obj.id)
return form
Note that this is a on-size-fits-all solution that automatically finds self-referencing model fields and removes self from all of them :-)
I like the solution of checking at save() time:
def save(self, *args, **kwargs):
# call full_clean() that in turn will call clean()
self.full_clean()
return super().save(*args, **kwargs)
def clean(self):
obj = self
parents = set()
while obj is not None:
if obj in parents:
raise ValidationError('Loop error', code='infinite_loop')
parents.add(obj)
obj = obj.parent