Django - calling custom update behaviour when overriding its behavour - django

What I am trying to do is best described by example. Consider the following:
class QuerySetManager(models.Manager):
def get_queryset(self):
result = self.model.QuerySet(self.model)
try:
result = result.filter(is_deleted=False)
except FieldError:
pass
return result
class MyModel(model.Models):
# core fields
objects = QuerySetManager()
class Meta:
managed = False
db_table = 'my_model'
class QuerySet(QuerySet):
def update(self, *args, **kwargs):
if something_special:
# handle that special case
else:
# call custom update
In effect, I am trying to override the update method of QuerySet's superclass. If something special happens, I would like to implement update process myself, and otherwise - call the standard update method of the superclass.
Any help on what the correct syntax is?
UPDATE
Let me provide a bit detailed background.
I am using the pattern found here.
The entire architecture looks like this:
class DeleteMixin(models.Model):
is_deleted = models.BooleanField(default=False)
class Meta:
abstract = True
class QuerySetManager(models.Manager):
def get_queryset(self):
result = self.model.QuerySet(self.model)
try:
result = result.filter(is_deleted=False)
except FieldError:
pass
return result
class Sms(DeleteMixin):
# core fields
objects = QuerySetManager()
class Meta:
managed = False
db_table = 'sms'
class QuerySet(QuerySet):
def inbox(self, user):
return self.filter(sms_type_id = 1)
def outbox(self, user):
return self.filter(sms_type_id = 2)
def update(self, *args, **kwargs):
if something_special:
# handle that special case
else:
# the issue in question - call custom update
This architecture enables me to:
exclude records with is_deleted field equal to 1 (for those tables where there is such field)
use chainable filters like sms.objects.inbox().outbox()

It's not enitrely clear what you are asking. IF you are trying to add a custom update method to a Queryset's superclass update method like this:
class MyQuerySet(QuerySet):
def update(self, *args, **kwargs):
....
super(MyQuerySet,self).update(*args,**kwargs)
if you are trying to override the save method in the model
class MyModel(model.Models):
def save(self,*args,**kwargs):
...
super(MyModel,self).save(*args,**kwargs)
Update (pun intended)
The code sample I have posted above is correct. The QuerySet class definitely as an update method. as can be seen here:
https://github.com/django/django/blob/master/django/db/models/query.py#L630
And if you have sub classed QuerySet correctly you will not get the error. But you haven't
class QuerySet(QuerySet):
This is incorrect.

Eventually, I got it work this way:
def update(self, *args, **kwargs):
if something_special:
# handle that special case
else:
super(self.model.QuerySet, self).update(*args,**kwargs)

Related

Django: Accessing parent object in new linline object

I have been combing through the internet for quite some while without finding any solution to this problem.
What I am trying to do...
I have the following models:
class TrackingEventType(models.Model):
required_previous_event = models.ForeignKey(TrackingEventType)
class TrackingEvent(models.Model):
tracking = models.ForeignKey(Tracking)
class Tracking(models.Model):
last_event = models.ForeignKey(TrackingEvent)
Now the main model is Tracking, so my admin for Tracking looks like this:
class TrackingEventInline(admin.TabularInline):
model = TrackingEvent
extra = 0
class TrackingAdmin(admin.ModelAdmin):
inlines = [TrackingEventInline]
That's it for the current setup.
Now my quest:
In the TrackingAdmin, when I add new TrackingEvent inlines, I want to limit the options of TrackingEventType to onlye those, that are allowed to follow on the last TrackingEvent of the Tracking. (Tracking.last_event == TrackingEventType.required_previous_event).
For this, I would need to be able to access the related Tracking on the InlineTrackingEvent, to access the last_event and filter the options for TrackingEventType accordingly.
So I found this: Accessing parent model instance from modelform of admin inline, but when I set up TrackingEventInline accordingly:
class MyFormSet(forms.BaseInlineFormSet):
def _construct_form(self, i, **kwargs):
kwargs['parent_object'] = self.instance
print self.instance
return super(MyFormSet, self)._construct_form(i, **kwargs)
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
print kwargs
self.parent_object = kwargs.pop('parent_object')
super(MyForm, self).__init__(*args, **kwargs)
class TrackingEventInline(admin.TabularInline):
form = MyForm
formset = MyFormSet
model = TrackingEvent
extra = 0
I get a KeyError at /admin/.../tracking/2/change/ 'parent_object' from self.parent_object = kwargs.pop('parent_object')
Does anyone know how to solve this? Am I approaching the problem the wrong way? I guess this would be pretty easy in a custom form in the frontend, but I really want to use the admin, because the whole application is built to be used from the admin, and it would be a hell lot of work to build a custom admin interface just because of this problem :)
Ok, so posting on StackOverflow is always helping to get the problem straight. I was able to put together a solution that works for me.
It includes defining my own Form in a outer function, as well as defining two InlineAdmin objects for TrackingEvent (one for update / edit, one just for insert).
Here's the code:
def create_trackingevent_form(tracking):
"""
"""
class TrackingEventForm(forms.ModelForm):
"""
Form for Tracking Event Inline
"""
def clean(self):
"""
May not be needed anymore, since event type choices are limited when creating new event.
"""
next_eventtype = self.cleaned_data['event_type']
tracking = self.cleaned_data['tracking']
# get last event, this also ensures last_event gets updated everytime the change form for TrackingEvent is loaded
last_eventtype = tracking.set_last_event()
if last_eventtype:
last_eventtype = last_eventtype.event_type
pk = self.instance.pk
insert = pk == None
# check if the event is updated or newly created
if insert:
if next_eventtype.required_previous_event == last_eventtype:
pass
else:
raise forms.ValidationError('"{}" requires "{}" as last event, "{}" found. Possible next events: {}'.format(
next_eventtype,
next_eventtype.required_previous_event,
last_eventtype,
'"%s" ' % ', '.join(map(str, [x.name for x in tracking.next_tracking_eventtype_options()]))
)
)
else:
pass
return self.cleaned_data
def __init__(self, *args, **kwargs):
# You can use the outer function's 'tracking' here
self.parent_object = tracking
super(TrackingEventForm, self).__init__(*args, **kwargs)
self.fields['event_type'].queryset = tracking.next_tracking_eventtype_options()
#self.fields['event_type'].limit_choices_to = tracking.next_tracking_eventtype_options()
return TrackingEventForm
class TrackingEventInline(admin.TabularInline):
#form = MyForm
#formset = MyFormSet
model = TrackingEvent
extra = 0
#readonly_fields = ['datetime', 'event_type', 'note']
def has_add_permission(self, request):
return False
class AddTrackingEventInline(admin.TabularInline):
model = TrackingEvent
extra = 0
def has_change_permission(self, request, obj=None):
return False
def queryset(self, request):
return super(AddTrackingEventInline, self).queryset(request).none()
def get_formset(self, request, obj=None, **kwargs):
if obj:
self.form = create_trackingevent_form(obj)
return super(AddTrackingEventInline, self).get_formset(request, obj, **kwargs)
I hope this helps other people with the same problem.. Some credit to the Stack Overflow threads that helped me come up with this:
Prepopulating inlines based on the parent model in the Django Admin
Limit foreign key choices in select in an inline form in admin
https://docs.djangoproject.com/en/1.9/ref/models/instances/#django.db.models.Model.clean_fields
Please do not hesitate to ask questions if you have any

ModelChoiceField in forms.Form won't validate if queryset is overridden

I have a django ModelChoiceField that won't validate if I override the queryset.
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
form = PersonalNote()
form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)
The form.is_valid() error is: "Select a valid choice. That choice is not one of the available choices".
If Tile.objects.none() is replaced with Tile.objects.all() it validates, but loads far too much data from the database. I've also tried:
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
def __init__(self, *args, **kwargs):
yyy = kwargs.pop('yyy', None)
super(PersonalNote, self).__init__(*args, **kwargs)
if yyy:
self.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)
What might be wrong here? Note the real application also overrides the label, but that does not seem to be a factor here:
class ModelChoiceField2(forms.ModelChoiceField):
def label_from_instance(self, obj):
assert isinstance(obj,Tile)
return obj.child_title()
After 2 hours I found the solution. Because you specified a queryset of none in the class definition, when you instantiate that PersonalNote(request.POST) to be validated it is referenceing a null query set
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
To fix this, when you create your form based on a POST request be sure to overwrite your queryset AGAIN before you check is_valid()
def some_view_def(request):
form = PersonalNote(request.POST)
**form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)**
if form.is_valid():
#Do whatever it is
When you pass an empty queryset to ModelChoiceField you're saying that nothing will be valid for that field. Perhaps you could filter the queryset so there aren't too many options.
I also had this problem. The idea is to dynamically change the queryset of a ModelChoiceField based on a condition (in my case it was a filter made by another ModelChoiceField).
So, having the next model as example:
class FilterModel(models.Model):
name = models.CharField()
class FooModel(models.Model):
filter_field = models.ForeignKey(FilterModel)
name = models.CharField()
class MyModel(models.Model):
foo_field = models.ForeignKey(FooModel)
As you can see, MyModel has a foreign key with FooModel, but not with FilterModel. So, in order to filter the FooModel options, I added a new ModelChoiceField on my form:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
# your code here
self.fields['my_filter_field'] = forms.ModelChoiceField(FilterModel, initial=my_filter_field_selected)
self.fields['my_filter_field'].queryset = FilterModel.objects.all()
Then, on your Front-End you can use Ajax to load the options of foo_field, based on the selected value of my_filter_field. At this point everyting should be working. But, when the form is loaded, it will bring all the posible options from FooModel. To avoid this, you need to dynamically change the queryset of foo_field.
On my form view, I passed a new argument to MyForm:
id_filter_field = request.POST.get('my_filter_field', None)
form = MyForm(data=request.POST, id_filter_field=id_filter_field)
Now, you can use that argument on MyForm to change the queryset:
class MyForm(forms.ModelForm):
# your code here
def __init__(self, *args, **kwargs):
self.id_filter_field = kwargs.pop('id_filter_field', None)
# your code here
if self.id_filter_field:
self.fields['foo_field'].queryset = FooModel.objects.filter(filter_field_id=self.id_filter_field)
else:
self.fields['foo_field'].queryset = FooModel.objects.none()

Django rest framework, use different serializers in the same ModelViewSet

I would like to provide two different serializers and yet be able to benefit from all the facilities of ModelViewSet:
When viewing a list of objects, I would like each object to have an url which redirects to its details and every other relation appear using __unicode __ of the target model;
example:
{
"url": "http://127.0.0.1:8000/database/gruppi/2/",
"nome": "universitari",
"descrizione": "unitn!",
"creatore": "emilio",
"accesso": "CHI",
"membri": [
"emilio",
"michele",
"luisa",
"ivan",
"saverio"
]
}
When viewing the details of an object, I would like to use the default HyperlinkedModelSerializer
example:
{
"url": "http://127.0.0.1:8000/database/gruppi/2/",
"nome": "universitari",
"descrizione": "unitn!",
"creatore": "http://127.0.0.1:8000/database/utenti/3/",
"accesso": "CHI",
"membri": [
"http://127.0.0.1:8000/database/utenti/3/",
"http://127.0.0.1:8000/database/utenti/4/",
"http://127.0.0.1:8000/database/utenti/5/",
"http://127.0.0.1:8000/database/utenti/6/",
"http://127.0.0.1:8000/database/utenti/7/"
]
}
I managed to make all this work as I wish in the following way:
serializers.py
# serializer to use when showing a list
class ListaGruppi(serializers.HyperlinkedModelSerializer):
membri = serializers.RelatedField(many = True)
creatore = serializers.RelatedField(many = False)
class Meta:
model = models.Gruppi
# serializer to use when showing the details
class DettaglioGruppi(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Gruppi
views.py
class DualSerializerViewSet(viewsets.ModelViewSet):
"""
ViewSet providing different serializers for list and detail views.
Use list_serializer and detail_serializer to provide them
"""
def list(self, *args, **kwargs):
self.serializer_class = self.list_serializer
return viewsets.ModelViewSet.list(self, *args, **kwargs)
def retrieve(self, *args, **kwargs):
self.serializer_class = self.detail_serializer
return viewsets.ModelViewSet.retrieve(self, *args, **kwargs)
class GruppiViewSet(DualSerializerViewSet):
model = models.Gruppi
list_serializer = serializers.ListaGruppi
detail_serializer = serializers.DettaglioGruppi
# etc.
Basically I detect when the user is requesting a list view or a detailed view and change serializer_class to suit my needs. I am not really satisfied with this code though, it looks like a dirty hack and, most importantly, what if two users request a list and a detail at the same moment?
Is there a better way to achieve this using ModelViewSets or do I have to fall back using GenericAPIView?
EDIT:
Here's how to do it using a custom base ModelViewSet:
class MultiSerializerViewSet(viewsets.ModelViewSet):
serializers = {
'default': None,
}
def get_serializer_class(self):
return self.serializers.get(self.action,
self.serializers['default'])
class GruppiViewSet(MultiSerializerViewSet):
model = models.Gruppi
serializers = {
'list': serializers.ListaGruppi,
'detail': serializers.DettaglioGruppi,
# etc.
}
Override your get_serializer_class method. This method is used in your model mixins to retrieve the proper Serializer class.
Note that there is also a get_serializer method which returns an instance of the correct Serializer
class DualSerializerViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.action == 'list':
return serializers.ListaGruppi
if self.action == 'retrieve':
return serializers.DettaglioGruppi
return serializers.Default # I dont' know what you want for create/destroy/update.
You may find this mixin useful, it overrides the get_serializer_class method and allows you to declare a dict that maps action and serializer class or fallback to the usual behavior.
class MultiSerializerViewSetMixin(object):
def get_serializer_class(self):
"""
Look for serializer class in self.serializer_action_classes, which
should be a dict mapping action name (key) to serializer class (value),
i.e.:
class MyViewSet(MultiSerializerViewSetMixin, ViewSet):
serializer_class = MyDefaultSerializer
serializer_action_classes = {
'list': MyListSerializer,
'my_action': MyActionSerializer,
}
#action
def my_action:
...
If there's no entry for that action then just fallback to the regular
get_serializer_class lookup: self.serializer_class, DefaultSerializer.
"""
try:
return self.serializer_action_classes[self.action]
except (KeyError, AttributeError):
return super(MultiSerializerViewSetMixin, self).get_serializer_class()
This answer is the same as the accepted answer but I prefer to do in this way.
Generic views
get_serializer_class(self):
Returns the class that should be used for the serializer. Defaults to returning the serializer_class attribute.
May be overridden to provide dynamic behavior, such as using different serializers for reading and write operations or providing different serializers to the different types of users.
the serializer_class attribute.
class DualSerializerViewSet(viewsets.ModelViewSet):
# mapping serializer into the action
serializer_classes = {
'list': serializers.ListaGruppi,
'retrieve': serializers.DettaglioGruppi,
# ... other actions
}
default_serializer_class = DefaultSerializer # Your default serializer
def get_serializer_class(self):
return self.serializer_classes.get(self.action, self.default_serializer_class)
Regarding providing different serializers, why is nobody going for the approach that checks the HTTP method? It's clearer IMO and requires no extra checks.
def get_serializer_class(self):
if self.request.method == 'POST':
return NewRackItemSerializer
return RackItemSerializer
Credits/source: https://github.com/encode/django-rest-framework/issues/1563#issuecomment-42357718
Based on #gonz and #user2734679 answers I've created this small python package that gives this functionality in form a child class of ModelViewset. Here is how it works.
from drf_custom_viewsets.viewsets.CustomSerializerViewSet
from myapp.serializers import DefaltSerializer, CustomSerializer1, CustomSerializer2
class MyViewSet(CustomSerializerViewSet):
serializer_class = DefaultSerializer
custom_serializer_classes = {
'create': CustomSerializer1,
'update': CustomSerializer2,
}
Just want to addon to existing solutions. If you want a different serializer for your viewset's extra actions (i.e. using #action decorator), you can add kwargs in the decorator like so:
#action(methods=['POST'], serializer_class=YourSpecialSerializer)
def your_extra_action(self, request):
serializer = self.get_serializer(data=request.data)
...
Although pre-defining multiple Serializers in or way or another does seem to be the most obviously documented way, FWIW there is an alternative approach that draws on other documented code and which enables passing arguments to the serializer as it is instantiated. I think it would probably tend to be more worthwhile if you needed to generate logic based on various factors, such as user admin levels, the action being called, perhaps even attributes of the instance.
The first piece of the puzzle is the documentation on dynamically modifying a serializer at the point of instantiation. That documentation doesn't explain how to call this code from a viewset or how to modify the readonly status of fields after they've been initated - but that's not very hard.
The second piece - the get_serializer method is also documented - (just a bit further down the page from get_serializer_class under 'other methods') so it should be safe to rely on (and the source is very simple, which hopefully means less chance of unintended side effects resulting from modification). Check the source under the GenericAPIView (the ModelViewSet - and all the other built in viewset classes it seems - inherit from the GenericAPIView which, defines get_serializer.
Putting the two together you could do something like this:
In a serializers file (for me base_serializers.py):
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Adding this next line to the documented example
read_only_fields = kwargs.pop('read_only_fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# another bit we're adding to documented example, to take care of readonly fields
if read_only_fields is not None:
for f in read_only_fields:
try:
self.fields[f].read_only = True
exceptKeyError:
#not in fields anyway
pass
Then in your viewset you might do something like this:
class MyViewSet(viewsets.ModelViewSet):
# ...permissions and all that stuff
def get_serializer(self, *args, **kwargs):
# the next line is taken from the source
kwargs['context'] = self.get_serializer_context()
# ... then whatever logic you want for this class e.g:
if self.action == "list":
rofs = ('field_a', 'field_b')
fs = ('field_a', 'field_c')
if self.action == “retrieve”:
rofs = ('field_a', 'field_c’, ‘field_d’)
fs = ('field_a', 'field_b’)
# add all your further elses, elifs, drawing on info re the actions,
# the user, the instance, anything passed to the method to define your read only fields and fields ...
# and finally instantiate the specific class you want (or you could just
# use get_serializer_class if you've defined it).
# Either way the class you're instantiating should inherit from your DynamicFieldsModelSerializer
kwargs['read_only_fields'] = rofs
kwargs['fields'] = fs
return MyDynamicSerializer(*args, **kwargs)
And that should be it! Using MyViewSet should now instantiate your MyDynamicSerializer with the arguments you'd like - and assuming your serializer inherits from your DynamicFieldsModelSerializer, it should know just what to do.
Perhaps its worth mentioning that it can makes special sense if you want to adapt the serializer in some other ways …e.g. to do things like take in a read_only_exceptions list and use it to whitelist rather than blacklist fields (which I tend to do). I also find it useful to set the fields to an empty tuple if its not passed and then just remove the check for None ... and I set my fields definitions on my inheriting Serializers to 'all'. This means no fields that aren't passed when instantiating the serializer survive by accident and I also don't have to compare the serializer invocation with the inheriting serializer class definition to know what's been included...e.g within the init of the DynamicFieldsModelSerializer:
# ....
fields = kwargs.pop('fields', ())
# ...
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# ....
NB If I just wanted two or three classes that mapped to distinct actions and/or I didn't want any specially dynamic serializer behaviour, I might well use one of the approaches mentioned by others here, but I thought this worth presenting as an alternative, particularly given its other uses.
With all other solutions mentioned, I was unable to find how to instantiate the class using get_serializer_class function and unable to find custom validation function as well. For those who are still lost just like I was and want full implementation please check the answer below.
views.py
from rest_framework.response import Response
from project.models import Project
from project.serializers import ProjectCreateSerializer, ProjectIDGeneratorSerializer
class ProjectViewSet(viewsets.ModelViewSet):
action_serializers = {
'generate_id': ProjectIDGeneratorSerializer,
'create': ProjectCreateSerializer,
}
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if hasattr(self, 'action_serializers'):
return self.action_serializers.get(self.action, self.serializer_class)
return super(ProjectViewSet, self).get_serializer_class()
# You can create custom function
def generate_id(self, request):
serializer = self.get_serializer_class()(data=request.GET)
serializer.context['user'] = request.user
serializer.is_valid(raise_exception=True)
return Response(serializer.validated_data, status=status.HTTP_200_OK)
def create(self, request, **kwargs):
serializer = self.get_serializer_class()(data=request.data)
serializer.context['user'] = request.user
serializer.is_valid(raise_exception=True)
return Response(serializer.validated_data, status=status.HTTP_200_OK)
serializers.py
import random
from rest_framework import serializers
from project.models import Project
class ProjectIDGeneratorSerializer(serializers.Serializer):
def update(self, instance, validated_data):
pass
def create(self, validated_data):
pass
projectName = serializers.CharField(write_only=True)
class Meta:
fields = ['projectName']
def validate(self, attrs):
project_name = attrs.get('projectName')
project_id = project_name.replace(' ', '-')
return {'projectID': project_id}
class ProjectCreateSerializer(serializers.Serializer):
def update(self, instance, validated_data):
pass
def create(self, validated_data):
pass
projectName = serializers.CharField(write_only=True)
projectID = serializers.CharField(write_only=True)
class Meta:
model = Project
fields = ['projectName', 'projectID']
def to_representation(self, instance: Project):
data = dict()
data['projectName'] = instance.name
data['projectID'] = instance.projectID
data['createdAt'] = instance.createdAt
data['updatedAt'] = instance.updatedAt
representation = {
'message': f'Project {instance.name} has been created.',
}
return representation
def validate(self, attrs):
print('attrs', dict(attrs))
project_name = attrs.get('projectName')
project_id = attrs.get('projectID')
if Project.objects.filter(projectID=project_id).first():
raise serializers.ValidationError(f'Project with ID {project_id} already exist')
project = Project.objects.create(projectID=project_id,
name=project_name)
print('user', self.context['user'])
project.user.add(self.context["user"])
project.save()
return self.to_representation(project)
urls.py
from django.urls import path
from .views import ProjectViewSet
urlpatterns = [
path('project/generateID', ProjectViewSet.as_view({'get': 'generate_id'})),
path('project/create', ProjectViewSet.as_view({'post': 'create'})),
]
models.py
# Create your models here.
from django.db import models
from authentication.models import User
class Project(models.Model):
id = models.AutoField(primary_key=True)
projectID = models.CharField(max_length=255, blank=False, db_index=True, null=False)
user = models.ManyToManyField(User)
name = models.CharField(max_length=255, blank=False)
createdAt = models.DateTimeField(auto_now_add=True)
updatedAt = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
You can map your all serializers with the action using a dictionary in class and then get them from "get_serializer_class" method. Here is what I am using to get different serializers in different cases.
class RushesViewSet(viewsets.ModelViewSet):
serializer_class = DetailedRushesSerializer
queryset = Rushes.objects.all().order_by('ingested_on')
permission_classes = (IsAuthenticated,)
filter_backends = (filters.SearchFilter,
django_filters.rest_framework.DjangoFilterBackend, filters.OrderingFilter)
pagination_class = ShortResultsSetPagination
search_fields = ('title', 'asset_version__title',
'asset_version__video__title')
filter_class = RushesFilter
action_serializer_classes = {
"create": RushesSerializer,
"update": RushesSerializer,
"retrieve": DetailedRushesSerializer,
"list": DetailedRushesSerializer,
"partial_update": RushesSerializer,
}
def get_serializer_context(self):
return {'request': self.request}
def get_serializer_class(self):
try:
return self.action_serializer_classes[self.action]
except (KeyError, AttributeError):
error_logger.error("---Exception occurred---")
return super(RushesViewSet, self).get_serializer_class()

Django: Force select related?

I've created a model, and I'm rendering the default/unmodified model form for it. This alone generates 64 SQL queries because it has quite a few foreign keys, and those in turn have more foreign keys.
Is it possible to force it to always (by default) perform a select_related every time one of these models are returned?
You can create a custom manager, and simply override get_queryset for it to apply everywhere. For example:
class MyManager(models.Manager):
def get_queryset(self):
return super(MyManager, self).get_queryset().select_related('foo', 'bar')
(Prior to Django 1.6, it was get_query_set).
Here's also a fun trick:
class DefaultSelectOrPrefetchManager(models.Manager):
def __init__(self, *args, **kwargs):
self._select_related = kwargs.pop('select_related', None)
self._prefetch_related = kwargs.pop('prefetch_related', None)
super(DefaultSelectOrPrefetchManager, self).__init__(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
qs = super(DefaultSelectOrPrefetchManager, self).get_queryset(*args, **kwargs)
if self._select_related:
qs = qs.select_related(*self._select_related)
if self._prefetch_related:
qs = qs.prefetch_related(*self._prefetch_related)
return qs
class Sandwich(models.Model):
bread = models.ForeignKey(Bread)
extras = models.ManyToManyField(Extra)
# ...
objects = DefaultSelectOrPrefetchManager(select_related=('bread',), prefetch_related=('extras',))
Then you can re-use the manager easily between model classes. As an example use case, this would be appropriate if you had a __unicode__ method on the model which rendered a string that included some information from a related model (or anything else that meant a related model was almost always required).
...and if you really want to get wacky, here's a more generalized version. It allows you to call any sequence of methods on the default queryset with any combination of args or kwargs. There might be some errors in the code, but you get the idea.
from django.db import models
class MethodCalls(object):
"""
A mock object which logs chained method calls.
"""
def __init__(self):
self._calls = []
def __getattr__(self, name):
c = Call(self, name)
self._calls.append(c)
return c
def __iter__(self):
for c in self._calls:
yield tuple(c)
class Call(object):
"""
Used by `MethodCalls` objects internally to represent chained method calls.
"""
def __init__(self, calls_obj, method_name):
self._calls = calls_obj
self.method_name = method_name
def __call__(self, *method_args, **method_kwargs):
self.method_args = method_args
self.method_kwargs = method_kwargs
return self._calls
def __iter__(self):
yield self.method_name
yield self.method_args
yield self.method_kwargs
class DefaultQuerysetMethodCallsManager(models.Manager):
"""
A model manager class which allows specification of a sequence of
method calls to be applied by default to base querysets.
`DefaultQuerysetMethodCallsManager` instances expose a property
`default_queryset_method_calls` to which chained method calls can be
applied to indicate which methods should be called on base querysets.
"""
def __init__(self, *args, **kwargs):
self.default_queryset_method_calls = MethodCalls()
super(DefaultQuerysetMethodCallsManager, self).__init__(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
qs = super(DefaultQuerysetMethodCallsManager, self).get_queryset(*args, **kwargs)
for method_name, method_args, method_kwargs in self.default_queryset_method_calls:
qs = getattr(qs, method_name)(*method_args, **method_kwargs)
return qs
class Sandwich(models.Model):
bread = models.ForeignKey(Bread)
extras = models.ManyToManyField(Extra)
# Other field definitions...
objects = DefaultQuerysetMethodCallsManager()
objects.default_queryset_method_calls.filter(
bread__type='wheat',
).select_related(
'bread',
).prefetch_related(
'extras',
)
The python-mock-inspired MethodCalls object is an attempt at making the API more natural. Some might find that a bit confusing. If so, you could sub out that code for an __init__ arg or kwarg that just accepts a tuple of method call information.
Create a custom models.Manager and override all the methods (filter, get etc.) and append select_related onto every query. Then set this manager as the objects attribute on the model.
I would recommend just going through your code and adding the select_related where needed, because doing select_related on everything is going to cause some serious performance issues down the line (and it wouldn't be entirely clear where it's coming from).

When saving, how can you check if a field has changed?

In my model I have :
class Alias(MyBaseModel):
remote_image = models.URLField(
max_length=500, null=True,
help_text='''
A URL that is downloaded and cached for the image.
Only used when the alias is made
'''
)
image = models.ImageField(
upload_to='alias', default='alias-default.png',
help_text="An image representing the alias"
)
def save(self, *args, **kw):
if (not self.image or self.image.name == 'alias-default.png') and self.remote_image :
try :
data = utils.fetch(self.remote_image)
image = StringIO.StringIO(data)
image = Image.open(image)
buf = StringIO.StringIO()
image.save(buf, format='PNG')
self.image.save(
hashlib.md5(self.string_id).hexdigest() + ".png", ContentFile(buf.getvalue())
)
except IOError :
pass
Which works great for the first time the remote_image changes.
How can I fetch a new image when someone has modified the remote_image on the alias? And secondly, is there a better way to cache a remote image?
Essentially, you want to override the __init__ method of models.Model so that you keep a copy of the original value. This makes it so that you don't have to do another DB lookup (which is always a good thing).
class Person(models.Model):
name = models.CharField()
__original_name = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__original_name = self.name
def save(self, force_insert=False, force_update=False, *args, **kwargs):
if self.name != self.__original_name:
# name changed - do something here
super().save(force_insert, force_update, *args, **kwargs)
self.__original_name = self.name
I use following mixin:
from django.forms.models import model_to_dict
class ModelDiffMixin(object):
"""
A model mixin that tracks model fields' values and provide some useful api
to know what fields have been changed.
"""
def __init__(self, *args, **kwargs):
super(ModelDiffMixin, self).__init__(*args, **kwargs)
self.__initial = self._dict
#property
def diff(self):
d1 = self.__initial
d2 = self._dict
diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
return dict(diffs)
#property
def has_changed(self):
return bool(self.diff)
#property
def changed_fields(self):
return self.diff.keys()
def get_field_diff(self, field_name):
"""
Returns a diff for field if it's changed and None otherwise.
"""
return self.diff.get(field_name, None)
def save(self, *args, **kwargs):
"""
Saves model and set initial state.
"""
super(ModelDiffMixin, self).save(*args, **kwargs)
self.__initial = self._dict
#property
def _dict(self):
return model_to_dict(self, fields=[field.name for field in
self._meta.fields])
Usage:
>>> p = Place()
>>> p.has_changed
False
>>> p.changed_fields
[]
>>> p.rank = 42
>>> p.has_changed
True
>>> p.changed_fields
['rank']
>>> p.diff
{'rank': (0, 42)}
>>> p.categories = [1, 3, 5]
>>> p.diff
{'categories': (None, [1, 3, 5]), 'rank': (0, 42)}
>>> p.get_field_diff('categories')
(None, [1, 3, 5])
>>> p.get_field_diff('rank')
(0, 42)
>>>
Note
Please note that this solution works well in context of current request only. Thus it's suitable primarily for simple cases. In concurrent environment where multiple requests can manipulate the same model instance at the same time, you definitely need a different approach.
Best way is with a pre_save signal. May not have been an option back in '09 when this question was asked and answered, but anyone seeing this today should do it this way:
#receiver(pre_save, sender=MyModel)
def do_something_if_changed(sender, instance, **kwargs):
try:
obj = sender.objects.get(pk=instance.pk)
except sender.DoesNotExist:
pass # Object is new, so field hasn't technically changed, but you may want to do something else here.
else:
if not obj.some_field == instance.some_field: # Field has changed
# do something
And now for direct answer: one way to check if the value for the field has changed is to fetch original data from database before saving instance. Consider this example:
class MyModel(models.Model):
f1 = models.CharField(max_length=1)
def save(self, *args, **kw):
if self.pk is not None:
orig = MyModel.objects.get(pk=self.pk)
if orig.f1 != self.f1:
print 'f1 changed'
super(MyModel, self).save(*args, **kw)
The same thing applies when working with a form. You can detect it at the clean or save method of a ModelForm:
class MyModelForm(forms.ModelForm):
def clean(self):
cleaned_data = super(ProjectForm, self).clean()
#if self.has_changed(): # new instance or existing updated (form has data to save)
if self.instance.pk is not None: # new instance only
if self.instance.f1 != cleaned_data['f1']:
print 'f1 changed'
return cleaned_data
class Meta:
model = MyModel
exclude = []
Since Django 1.8 released, you can use from_db classmethod to cache old value of remote_image. Then in save method you can compare old and new value of field to check if the value has changed.
#classmethod
def from_db(cls, db, field_names, values):
new = super(Alias, cls).from_db(db, field_names, values)
# cache value went from the base
new._loaded_remote_image = values[field_names.index('remote_image')]
return new
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if (self._state.adding and self.remote_image) or \
(not self._state.adding and self._loaded_remote_image != self.remote_image):
# If it is first save and there is no cached remote_image but there is new one,
# or the value of remote_image has changed - do your stuff!
Note that field change tracking is available in django-model-utils.
https://django-model-utils.readthedocs.org/en/latest/index.html
If you are using a form, you can use Form's changed_data (docs):
class AliasForm(ModelForm):
def save(self, commit=True):
if 'remote_image' in self.changed_data:
# do things
remote_image = self.cleaned_data['remote_image']
do_things(remote_image)
super(AliasForm, self).save(commit)
class Meta:
model = Alias
I am a bit late to the party but I found this solution also:
Django Dirty Fields
Another late answer, but if you're just trying to see if a new file has been uploaded to a file field, try this: (adapted from Christopher Adams's comment on the link http://zmsmith.com/2010/05/django-check-if-a-field-has-changed/ in zach's comment here)
Updated link: https://web.archive.org/web/20130101010327/http://zmsmith.com:80/2010/05/django-check-if-a-field-has-changed/
def save(self, *args, **kw):
from django.core.files.uploadedfile import UploadedFile
if hasattr(self.image, 'file') and isinstance(self.image.file, UploadedFile) :
# Handle FileFields as special cases, because the uploaded filename could be
# the same as the filename that's already there even though there may
# be different file contents.
# if a file was just uploaded, the storage model with be UploadedFile
# Do new file stuff here
pass
There is an attribute __dict__ which have all the fields as the keys and value as the field values. So we can just compare two of them
Just change the save function of model to the function below
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
if self.pk is not None:
initial = A.objects.get(pk=self.pk)
initial_json, final_json = initial.__dict__.copy(), self.__dict__.copy()
initial_json.pop('_state'), final_json.pop('_state')
only_changed_fields = {k: {'final_value': final_json[k], 'initial_value': initial_json[k]} for k in initial_json if final_json[k] != initial_json[k]}
print(only_changed_fields)
super(A, self).save(force_insert=False, force_update=False, using=None, update_fields=None)
Example Usage:
class A(models.Model):
name = models.CharField(max_length=200, null=True, blank=True)
senior = models.CharField(choices=choices, max_length=3)
timestamp = models.DateTimeField(null=True, blank=True)
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
if self.pk is not None:
initial = A.objects.get(pk=self.pk)
initial_json, final_json = initial.__dict__.copy(), self.__dict__.copy()
initial_json.pop('_state'), final_json.pop('_state')
only_changed_fields = {k: {'final_value': final_json[k], 'initial_value': initial_json[k]} for k in initial_json if final_json[k] != initial_json[k]}
print(only_changed_fields)
super(A, self).save(force_insert=False, force_update=False, using=None, update_fields=None)
yields output with only those fields that have been changed
{'name': {'initial_value': '1234515', 'final_value': 'nim'}, 'senior': {'initial_value': 'no', 'final_value': 'yes'}}
As of Django 1.8, there's the from_db method, as Serge mentions. In fact, the Django docs include this specific use case as an example:
https://docs.djangoproject.com/en/dev/ref/models/instances/#customizing-model-loading
Below is an example showing how to record the initial values of fields that are loaded from the database
This works for me in Django 1.8
def clean(self):
if self.cleaned_data['name'] != self.initial['name']:
# Do something
Very late to the game, but this is a version of Chris Pratt's answer that protects against race conditions while sacrificing performance, by using a transaction block and select_for_update()
#receiver(pre_save, sender=MyModel)
#transaction.atomic
def do_something_if_changed(sender, instance, **kwargs):
try:
obj = sender.objects.select_for_update().get(pk=instance.pk)
except sender.DoesNotExist:
pass # Object is new, so field hasn't technically changed, but you may want to do something else here.
else:
if not obj.some_field == instance.some_field: # Field has changed
# do something
You can use django-model-changes to do this without an additional database lookup:
from django.dispatch import receiver
from django_model_changes import ChangesMixin
class Alias(ChangesMixin, MyBaseModel):
# your model
#receiver(pre_save, sender=Alias)
def do_something_if_changed(sender, instance, **kwargs):
if 'remote_image' in instance.changes():
# do something
The optimal solution is probably one that does not include an additional database read operation prior to saving the model instance, nor any further django-library. This is why laffuste's solutions is preferable. In the context of an admin site, one can simply override the save_model-method, and invoke the form's has_changed method there, just as in Sion's answer above. You arrive at something like this, drawing on Sion's example setting but using changed_data to get every possible change:
class ModelAdmin(admin.ModelAdmin):
fields=['name','mode']
def save_model(self, request, obj, form, change):
form.changed_data #output could be ['name']
#do somethin the changed name value...
#call the super method
super(self,ModelAdmin).save_model(request, obj, form, change)
Override save_model:
https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model
Built-in changed_data-method for a Field:
https://docs.djangoproject.com/en/1.10/ref/forms/api/#django.forms.Form.changed_data
While this doesn't actually answer your question, I'd go about this in a different way.
Simply clear the remote_image field after successfully saving the local copy. Then in your save method you can always update the image whenever remote_image isn't empty.
If you'd like to keep a reference to the url, you could use an non-editable boolean field to handle the caching flag rather than remote_image field itself.
I had this situation before my solution was to override the pre_save() method of the target field class it will be called only if the field has been changed
useful with FileField
example:
class PDFField(FileField):
def pre_save(self, model_instance, add):
# do some operations on your file
# if and only if you have changed the filefield
disadvantage:
not useful if you want to do any (post_save) operation like using the created object in some job (if certain field has changed)
I have extended the mixin of #livskiy as follows:
class ModelDiffMixin(models.Model):
"""
A model mixin that tracks model fields' values and provide some useful api
to know what fields have been changed.
"""
_dict = DictField(editable=False)
def __init__(self, *args, **kwargs):
super(ModelDiffMixin, self).__init__(*args, **kwargs)
self._initial = self._dict
#property
def diff(self):
d1 = self._initial
d2 = self._dict
diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
return dict(diffs)
#property
def has_changed(self):
return bool(self.diff)
#property
def changed_fields(self):
return self.diff.keys()
def get_field_diff(self, field_name):
"""
Returns a diff for field if it's changed and None otherwise.
"""
return self.diff.get(field_name, None)
def save(self, *args, **kwargs):
"""
Saves model and set initial state.
"""
object_dict = model_to_dict(self,
fields=[field.name for field in self._meta.fields])
for field in object_dict:
# for FileFields
if issubclass(object_dict[field].__class__, FieldFile):
try:
object_dict[field] = object_dict[field].path
except :
object_dict[field] = object_dict[field].name
# TODO: add other non-serializable field types
self._dict = object_dict
super(ModelDiffMixin, self).save(*args, **kwargs)
class Meta:
abstract = True
and the DictField is:
class DictField(models.TextField):
__metaclass__ = models.SubfieldBase
description = "Stores a python dict"
def __init__(self, *args, **kwargs):
super(DictField, self).__init__(*args, **kwargs)
def to_python(self, value):
if not value:
value = {}
if isinstance(value, dict):
return value
return json.loads(value)
def get_prep_value(self, value):
if value is None:
return value
return json.dumps(value)
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value)
it can be used by extending it in your models
a _dict field will be added when you sync/migrate and that field will store the state of your objects
improving #josh answer for all fields:
class Person(models.Model):
name = models.CharField()
def __init__(self, *args, **kwargs):
super(Person, self).__init__(*args, **kwargs)
self._original_fields = dict([(field.attname, getattr(self, field.attname))
for field in self._meta.local_fields if not isinstance(field, models.ForeignKey)])
def save(self, *args, **kwargs):
if self.id:
for field in self._meta.local_fields:
if not isinstance(field, models.ForeignKey) and\
self._original_fields[field.name] != getattr(self, field.name):
# Do Something
super(Person, self).save(*args, **kwargs)
just to clarify, the getattr works to get fields like person.name with strings (i.e. getattr(person, "name")
My take on #iperelivskiy's solution: on large scale, creating the _initial dict for every __init__ is expensive, and most of the time - unnecessary. I have changed the mixin slightly such that it records changes only when you explicitly tell it to do so (by calling instance.track_changes):
from typing import KeysView, Optional
from django.forms import model_to_dict
class TrackChangesMixin:
_snapshot: Optional[dict] = None
def track_changes(self):
self._snapshot = self.as_dict
#property
def diff(self) -> dict:
if self._snapshot is None:
raise ValueError("track_changes wasn't called, can't determine diff.")
d1 = self._snapshot
d2 = self.as_dict
diffs = [(k, (v, d2[k])) for k, v in d1.items() if str(v) != str(d2[k])]
return dict(diffs)
#property
def has_changed(self) -> bool:
return bool(self.diff)
#property
def changed_fields(self) -> KeysView:
return self.diff.keys()
#property
def as_dict(self) -> dict:
return model_to_dict(self, fields=[field.name for field in self._meta.fields])
I have found this package django-lifecycle.
It uses django signals to define #hook decorator, which is very robust and reliable. I used it and it is a bliss.
How about using David Cramer's solution:
http://cramer.io/2010/12/06/tracking-changes-to-fields-in-django/
I've had success using it like this:
#track_data('name')
class Mode(models.Model):
name = models.CharField(max_length=5)
mode = models.CharField(max_length=5)
def save(self, *args, **kwargs):
if self.has_changed('name'):
print 'name changed'
# OR #
#classmethod
def post_save(cls, sender, instance, created, **kwargs):
if instance.has_changed('name'):
print "Hooray!"
A modification to #ivanperelivskiy's answer:
#property
def _dict(self):
ret = {}
for field in self._meta.get_fields():
if isinstance(field, ForeignObjectRel):
# foreign objects might not have corresponding objects in the database.
if hasattr(self, field.get_accessor_name()):
ret[field.get_accessor_name()] = getattr(self, field.get_accessor_name())
else:
ret[field.get_accessor_name()] = None
else:
ret[field.attname] = getattr(self, field.attname)
return ret
This uses django 1.10's public method get_fields instead. This makes the code more future proof, but more importantly also includes foreign keys and fields where editable=False.
For reference, here is the implementation of .fields
#cached_property
def fields(self):
"""
Returns a list of all forward fields on the model and its parents,
excluding ManyToManyFields.
Private API intended only to be used by Django itself; get_fields()
combined with filtering of field properties is the public API for
obtaining this field list.
"""
# For legacy reasons, the fields property should only contain forward
# fields that are not private or with a m2m cardinality. Therefore we
# pass these three filters as filters to the generator.
# The third lambda is a longwinded way of checking f.related_model - we don't
# use that property directly because related_model is a cached property,
# and all the models may not have been loaded yet; we don't want to cache
# the string reference to the related_model.
def is_not_an_m2m_field(f):
return not (f.is_relation and f.many_to_many)
def is_not_a_generic_relation(f):
return not (f.is_relation and f.one_to_many)
def is_not_a_generic_foreign_key(f):
return not (
f.is_relation and f.many_to_one and not (hasattr(f.remote_field, 'model') and f.remote_field.model)
)
return make_immutable_fields_list(
"fields",
(f for f in self._get_fields(reverse=False)
if is_not_an_m2m_field(f) and is_not_a_generic_relation(f) and is_not_a_generic_foreign_key(f))
)
as an extension of SmileyChris' answer, you can add a datetime field to the model for last_updated, and set some sort of limit for the max age you'll let it get to before checking for a change
The mixin from #ivanlivski is great.
I've extended it to
Ensure it works with Decimal fields.
Expose properties to simplify usage
The updated code is available here:
https://github.com/sknutsonsf/python-contrib/blob/master/src/django/utils/ModelDiffMixin.py
To help people new to Python or Django, I'll give a more complete example.
This particular usage is to take a file from a data provider and ensure the records in the database reflect the file.
My model object:
class Station(ModelDiffMixin.ModelDiffMixin, models.Model):
station_name = models.CharField(max_length=200)
nearby_city = models.CharField(max_length=200)
precipitation = models.DecimalField(max_digits=5, decimal_places=2)
# <list of many other fields>
def is_float_changed (self,v1, v2):
''' Compare two floating values to just two digit precision
Override Default precision is 5 digits
'''
return abs (round (v1 - v2, 2)) > 0.01
The class that loads the file has these methods:
class UpdateWeather (object)
# other methods omitted
def update_stations (self, filename):
# read all existing data
all_stations = models.Station.objects.all()
self._existing_stations = {}
# insert into a collection for referencing while we check if data exists
for stn in all_stations.iterator():
self._existing_stations[stn.id] = stn
# read the file. result is array of objects in known column order
data = read_tabbed_file(filename)
# iterate rows from file and insert or update where needed
for rownum in range(sh.nrows):
self._update_row(sh.row(rownum));
# now anything remaining in the collection is no longer active
# since it was not found in the newest file
# for now, delete that record
# there should never be any of these if the file was created properly
for stn in self._existing_stations.values():
stn.delete()
self._num_deleted = self._num_deleted+1
def _update_row (self, rowdata):
stnid = int(rowdata[0].value)
name = rowdata[1].value.strip()
# skip the blank names where data source has ids with no data today
if len(name) < 1:
return
# fetch rest of fields and do sanity test
nearby_city = rowdata[2].value.strip()
precip = rowdata[3].value
if stnid in self._existing_stations:
stn = self._existing_stations[stnid]
del self._existing_stations[stnid]
is_update = True;
else:
stn = models.Station()
is_update = False;
# object is new or old, don't care here
stn.id = stnid
stn.station_name = name;
stn.nearby_city = nearby_city
stn.precipitation = precip
# many other fields updated from the file
if is_update == True:
# we use a model mixin to simplify detection of changes
# at the cost of extra memory to store the objects
if stn.has_changed == True:
self._num_updated = self._num_updated + 1;
stn.save();
else:
self._num_created = self._num_created + 1;
stn.save()
Here is another way of doing it.
class Parameter(models.Model):
def __init__(self, *args, **kwargs):
super(Parameter, self).__init__(*args, **kwargs)
self.__original_value = self.value
def clean(self,*args,**kwargs):
if self.__original_value == self.value:
print("igual")
else:
print("distinto")
def save(self,*args,**kwargs):
self.full_clean()
return super(Parameter, self).save(*args, **kwargs)
self.__original_value = self.value
key = models.CharField(max_length=24, db_index=True, unique=True)
value = models.CharField(max_length=128)
As per documentation: validating objects
"The second step full_clean() performs is to call Model.clean(). This method should be overridden to perform custom validation on your model.
This method should be used to provide custom model validation, and to modify attributes on your model if desired. For instance, you could use it to automatically provide a value for a field, or to do validation that requires access to more than a single field:"
If you do not find interest in overriding save method, you can do
model_fields = [f.name for f in YourModel._meta.get_fields()]
valid_data = {
key: new_data[key]
for key in model_fields
if key in new_data.keys()
}
for (key, value) in valid_data.items():
if getattr(instance, key) != value:
print ('Data has changed')
setattr(instance, key, value)
instance.save()
Sometimes I want to check for changes on the same specific fields on multiple models that share those fields, so I define a list of those fields and use a signal. In this case, geocoding addresses only if something has changed, or if the entry is new:
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save, sender=SomeUserProfileModel)
#receiver(pre_save, sender=SomePlaceModel)
#receiver(pre_save, sender=SomeOrganizationModel)
#receiver(pre_save, sender=SomeContactInfoModel)
def geocode_address(sender, instance, *args, **kwargs):
input_fields = ['address_line', 'address_line_2', 'city', 'state', 'postal_code', 'country']
try:
orig = sender.objects.get(id=instance.id)
if orig:
changes = 0
for field in input_fields:
if not (getattr(instance, field)) == (getattr(orig, field)):
changes += 1
if changes > 0:
# do something here because at least one field changed...
my_geocoder_function(instance)
except:
# do something here because there is no original, or pass.
my_geocoder_function(instance)
Writing it once and attaching with "#receiver" sure beats overriding multiple model save methods, but perhaps some others have better ideas.