Update:
Well, I cobbled together a few things to accomplish most of what I
needed to accomplish, and figured I'd write it down here so I can look
it up later and so others can learn from it (the doc on m2m_changed,
in particular, is not incomplete, but not detailed or particularly
precise, and I had trouble finding a good example).
The event to event_date relation was one-to-many, so there is a model
for event dates that contains the key to each date's related event.
For these, I overrode the save and delete methods for the event_dates
model so it notifies the related event when any related date is added,
changed, or deleted, after the actual save on insert, update, or
delete, so the related event (which is still present in the instance
even on delete, though the relation no longer exists in the database)
can invoke a method that accepts the date and checks if it potentially
needs to re-calculate its overall start and end dates from the
database based on where the date lies in relation to its current start
and end dates. Part of the problem I was running into is that the
internal set of event_dates for an event were not getting updated as
updates from the admin screen were getting processed. The database is
updated, though, and so the event can query the database for updated
dates and update its start and end date accurately even though its
internal Set representation of associated dates gets out of sync.
For the ManyToMany, I ended up being able to use the m2m_changed
signal combined with overriding the save and delete on the right-hand
side of the m2m relationship to let the event know that something had
changed, and so it should rebuild its cached list of series and
providers. First, a few notes on what precisely the m2m_changed
signal passes:
- sender: Python new-class Type instance for the django-generated
"through" class that connects the two models (Event.presenters.through
and Event.series.through in this case).
- instance: an instance of the model that contains the ManyToManyField
relationship (Event, in this case).
- model: Python new-class Type instance for the class of the model
that is on the right side of the M2M relationship, joined by the
"through" class that is in the "sender" parameter (Series and
Presenter model classes in the two functions below).
- action - either "pre_add"/"post_add" or
"pre_clear"/"post_clear" ("pre_remove" and "post_remove" are never
sent by the admin, as far as I can tell, since it just clears all
relations each time a record is saved, then adds in relations again
that still are present - not perhaps how I would have done it, but
efficient).
Because of how django admin implements processing changes to m2m
relations (clear all, then re-add all that are present in form submit)
m2m_changed doesn't actually tell you how the relation changed (so no
signals for things getting removed, and no good signals on "add"
versus "still here"), so I had to just use these signals to let the
event know something had changed with a given set of relations, and
have it call a method to update itself from the database to figure out
what had changed and take the changes into account. This worked, but
wasn't quite as targeted as it could be if admin actually passed
m2m_changed signals for removal, and didn't pass add signals for
relations that were unchanged.
I also limited the event listeners to only the "through" senders I
cared about (see code sample below for accessing a "through" class -
import the class that contains the ManyToManyField, then reference
"class_name"."m2m_field_name".through). Here is the code I ended up
with for registering the signals (in my models.py file, so the Event
class is already there, doesn't need to be imported, and including the
debug and logging code I used to figure much of this out):
# imports for handling signals that ManyToMany relations have changed.
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
@receiver( m2m_changed, sender = Event.series.through )
def process_event_series_updates( sender, **kwargs_IN ):
# declare variables
instance_IN = None #-- kwargs_IN[ "instance" ]
action_IN = '' #-- kwargs_IN[ "action" ] - from
http://docs.djangoproject.com/en/dev/ref/signals/, will be either
"pre_add", "post_add", "pre_remove", "post_remove", "pre_clear", or
"post_clear"
reverse_IN = '' #-- kwargs_IN[ "reverse" ]
model_IN = None #-- kwargs_IN[ "model" ]
pk_set_IN = None #-- kwargs_IN[ "pk_set" ]
using_IN = None #-- kwargs_IN[ "using" ]
instance_IN = None #-- kwargs_IN[ instance ]
# just try reading the arguments, outputting a log message.
instance_IN = kwargs_IN[ "instance" ]
action_IN = kwargs_IN[ "action" ]
reverse_IN = kwargs_IN[ "reverse" ]
model_IN = kwargs_IN[ "model" ]
pk_set_IN = kwargs_IN[ "pk_set" ]
using_IN = kwargs_IN[ "using" ]
#logging.debug( "In process_event_series_updates(): We have a
signal. sender = " + str( sender ) + "; action = " + action_IN + ";
reverse = " + str( reverse_IN ) + "; model = " + str( model_IN ) + ";
pk_set = " + str( pk_set_IN ) + "; using = " + using_IN + ";
instance? = " + str( instance_IN ) + " (class: " +
str( instance_IN.__class__ ) + ")." )
# see if we are a "post_add" or "post_remove" action.
if ( ( action_IN == "post_add" ) or ( action_IN ==
"post_remove" ) ):
# we are a post-add. Rebuild the series field, then save.
instance_IN.update_series_name()
instance_IN.save()
#-- END check to see if we are post-add. --#
#-- end function process_event_series_updates() --#
@receiver( m2m_changed, sender = Event.presenters.through )
def process_event_presenter_updates( sender, **kwargs_IN ):
# declare variables
instance_IN = None #-- kwargs_IN[ "instance" ]
action_IN = '' #-- kwargs_IN[ "action" ] - from
http://docs.djangoproject.com/en/dev/ref/signals/, will be either
"pre_add", "post_add", "pre_remove", "post_remove", "pre_clear", or
"post_clear"
reverse_IN = '' #-- kwargs_IN[ "reverse" ]
model_IN = None #-- kwargs_IN[ "model" ]
pk_set_IN = None #-- kwargs_IN[ "pk_set" ]
using_IN = None #-- kwargs_IN[ "using" ]
instance_IN = None #-- kwargs_IN[ instance ]
# just try reading the arguments, outputting a log message.
instance_IN = kwargs_IN[ "instance" ]
action_IN = kwargs_IN[ "action" ]
reverse_IN = kwargs_IN[ "reverse" ]
model_IN = kwargs_IN[ "model" ]
pk_set_IN = kwargs_IN[ "pk_set" ]
using_IN = kwargs_IN[ "using" ]
#logging.debug( "In process_event_presenter_updates(): We have a
signal. sender = " + str( sender ) + "; action = " + action_IN + ";
reverse = " + str( reverse_IN ) + "; model = " + str( model_IN ) + ";
pk_set = " + str( pk_set_IN ) + "; using = " + using_IN + ";
instance? = " + str( instance_IN ) + " (class: " +
str( instance_IN.__class__ ) + ")." )
# see if we are a "post_add" or "post_remove" action.
if ( ( action_IN == "post_add" ) or ( action_IN ==
"post_remove" ) ):
# we are a post-add. Rebuild the series field, then save.
instance_IN.update_presenter()
instance_IN.save()
#-- END check to see if we are post-add. --#
#-- end function process_event_presenter_updates() --#
On Sep 18, 3:00 pm, "jonathan.morgan" <
jonathan.morgan....@gmail.com>
wrote: