Related models update on model field change - python

What is the most proper way to trigger update of related models, when one of the fields of parent model is changed? I have this set of models:
class ActivityObject(models.Model):
is_deleted = models.BooleanField(default=False)
class ActivityJob(models.Model):
activity_object = models.ForeignKey(
ActivityObject,
related_name='activity_jobs',
)
is_deleted = models.BooleanField(default=False)
so if I set ActivityObject.is_deleted = True on some instance all I want is that all related instances of ActivityJob also changed field is_deleted to True. Thanks in advance.

Overriding save() will work:
class ActivityObject(models.Model):
is_deleted = models.BooleanField(default=False)
def save(self, *args, **kwargs):
super(ActivityObject, self).save(args, kwargs)
if self.is_deleted:
for job in self.activity_jobs:
job.is_deleted = True
job.save()
Just guessing here, but if the real purpose of this is to delete ActivityJobs when related ActivityObjects are deleted, then you can just go ahead and delete the ActivityObject. Django's default behavior will remove all the ActivityJobs connected to it.
If you want to perform some other action when deleting, use Django's pre_delete or post_delete signals, which will call a function you define before/after deleting objects of the type you specify.
EDIT: if you ever use update() on querysets dealing with ActivityObject and changing is_deleted, you can either ensure that you perform a corresponding update() on ActivityJob, or you can override ActivityObject's queryset functionality like this to make it happen automatically.

You can use Django signals' pre_delete or post_delete. More details and examples are available in the Django Signals documentation.

Related

Django Custom Manager to dynamically filter archived objects

Suppose I have a model:
class Car(models.Model):
name = models.CharField(max_length=50)
is_active = models.BooleanField(default=True)
When I query on Car, I always want to return the objects satisfying, is_active=True.
For this, searching on StackOverFlow, I get that my best bet is to use ModelManager, like this:
class CarManager(models.ModelManager):
def get_queryset(self):
return super().get_queryset().filter(is_active=True)
And, use this Manager in my model.
class Car(models.Model):
name = models.CharField(max_length=50)
is_active = models.BooleanField(default=True)
objects = CarManager()
Using this solution always returns active Car queryset.
But, sometimes I want to return inactive Car queryset as well, and I don't want to write another ModelManager.
To elaborate,
When I run,
Car.objects.all()
or,
Car.objects.filter(name__contains='Car')
or,
Car.objects.filter(is_active=True)
I only want active Car queryset.
When I run,
Car.objects.filter(is_active=False)
I want to have inactive Car queryset.
And, I want to achieve this using single ModelManager and default methods (get, filter, all, etc). Why I want this is because, it has been used in many places already.
So, is there any way to achieve this? Any suggestions or insights are heartily welcome.
Thanks for your help in advance.
So, after a long research and going through documentations and Django source code, I have come up with this for filter() method:
class CarManager(models.ModelManager):
def filter(self, *args, **kwargs):
if kwargs.get('is_active') == False:
return super().get_queryset().filter(*args, **kwargs)
return self.get_queryset().filter(*args, **kwargs)
def get_queryset(self):
return super().get_queryset().filter(is_active=True)
Here, I have overriden filter() method, so that:
If is_active=False is passed, then the parent (default) get_queryset() followed by filter() is called.
If is_active is not passed or is_active=True is passed, then the overridden get_queryset() method is called (which returns the active Car queryset) followed by the filter() method.
If there are any other solutions or better practices, please do mention them. Thanks.
I am afraid is not possible since you are overwriting the manager's base queryset. What you could do instead of creating another manager is to implement an extra method that returns a queryset with only inactive cars, something like:
class CarManager(models.ModelManager):
def get_queryset(self):
return super().get_queryset().filter(is_active=True)
def get_inactive_cars(self):
return super().get_queryset().filter(is_active=False)
And then replace the querysets where you retrieve the inactive ones:
Car.objects.get_inactive_cars()

How can I implement a verified field in django?

In Django I'd like to add a field "verified" of type BooleanField to my models which shall indicate if the current model instance has been reviewed by a staff user member or not. Whenever a model instance field other than the "verified" field changed the verified field value shall be reset to False. Whenever only the "verified" field has been changed it's value shall be taken as is (most of the time True but potentially False as well).
One possibility would be to reset the "verified" field in post-save signals handlers considering update_fields passed to save(). However using signals seems to be considered an anti-pattern in almost all use cases. Instead one should override the save() method. But still when overriding save I'd have to determine update_fields manually somehow. Otherwise I've no information about which fields changed.
How can I implement something like this most easily. I'd prefer a solution using a third-party package w.o. custom hacks or a solution without any dependencies to other packages. However using django-model-utils monitorfield, django-dirtyfields for a custom implementation or something equivalent would be ok as well.
Using dirty-fields seems to be easiest to implement a verified field. So far I came up with something like follows:
DJANGO-APP/models.py:
from django.db import models
from dirtyfields import DirtyFieldsMixin
class VerifiedModel(DirtyFieldsMixin, models.Model):
"""
Abstract class which allows to extend models with user verification model field.
"""
ENABLE_M2M_CHECK = True
verified = models.BooleanField(
default=False,
help_text="The verification status. True means meta-data is verified. False means meta-data is not verified. The verification status is reset to False whenever a field is set.",
)
def _update_verified_field(self):
"""
To be called in inheriting model's save() method.
"""
if self.is_dirty():
if not 'verified' in self.get_dirty_fields():
self.verified = False
class Meta:
abstract = True
class ModelToBeVerified(VerifiedModel):
...
def save(self, *args, **kwargs):
...
self._update_verified_field()
return super(ModelToBeVerified, self).save(*args, **kwargs)

Django Related Object empty queryset on clean()

I'm trying access a list of related objects through the "related_set.all()" method whenever a model is saved from Django Admin. No matter what I do however the QuerySet is always empty... Here's what it looks like (I've removed a lot of (hopefully) irrelevant stuff)
class Board(models.Model):
group = models.OneToOneField(Group, on_delete=models.CASCADE)
def clean(self):
roles = self.role_set.all() # This just returns <QuerySet []>
... validation, etc....
class Role(models.Model):
board = models.ForeignKey(Board, on_delete=models.CASCADE)
members = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
Funny thing is that it used to work, but after setting the User to be the AUTH_USER_MODEL (after a lot of refactoring) it suddenly stopped returning anything in the QuerySet... For what it matters I'm using a NestedStackedInline in the Admin panel, and what I hope to achieve is that whenever the "Save"-button in the admin panel is called, it should call the clean() method on the Role-class, call the clean() method on the Board-class and upon success call the save() method on both.
I've been troubleshooting this for way too long now and would really appreciate if anyone have some pointers or ideas.

Django, override many-to-many field ModelManager

How can i override the model manager of a many-to-many field that i have considering the following:
class TermsManager(models.Manager):
def all(self):
return super(TermsManager, self).all().filter(condition_here)
class Term(models.Model):
objects = TermsManager()
name = models.CharField(max_length=255)
class Object(models.Model):
title = models.CharField(max_length=255)
terms = models.ManyToManyField(Term, blank=True)
class Channel(Object):
class Meta:
proxy = True
I also have a class which inherits from TermManager called ChannelTermManager.
How can i override the "terms" field of the Channel model so that
mychannel.terms calls the ChannelTermManager instead of TermManager?
First of all, you shouldn't be overriding all(). If you want to change the default queryset, override get_query_set like so:
class TermsManager(models.Manager):
def get_query_set(self):
return super(TermsManager, self).get_query_set().filter(condition_here)
This is because all() is often omitted when other queryset functions are chained on, and you want your queryset to behave the same whether all() is explicitly called or not.
But even so, what you're doing is still problematic. As explained in the documentation for managers, filtering the default related queryset will affect all sorts of automatic things behind the scenes (such as when dumping data to create backups/fixtures, etc.). You almost definitely do not want this. And you really don't want your related object managers doing this either (by setting use_for_related_fields = True), because you'll be masking what's actually stored in the database, rather than simply detecting out of date data and creating alerts or whatever to clean it up. use_for_related_fields is intended for creating managers that augment the normal capabilities of the vanilla manager, not to filter.
I had a similar situation to yours however, and I handled it like so:
class FilteredTermsManager(models.Manager):
def get_query_set(self):
return super(TermsManager, self).get_query_set().filter(condition_here)
class Term(models.Model):
allTerms = models.Manger() # Establish this as the default/automatic manager
objects = FilteredTermsManager()
name = models.CharField(max_length=255)
This way, I could do all my initial querying on the model through my filtered queryset and it looks like "regular Django", but all relational and behind the scenes queries would work on the unfiltered database. And I could always access the true full set of objects by manually doing Term.allTerms.all().
As for using different managers for different related objects, there's nothing you can really do there. But why not just add Channel specific objects to your custom manager, and simply not call them from methods that operate on get Term querysets from Object?

Overwrite method add for ManyToMany related fields

Where should I overwrite method add() for ManyToMany related fields.
Seems like it is not manager 'objects' of my model. Because when we are adding new relation for ManyToMany fields we are not writing Model.objects.add().
So what I need it overwrite method add() of instance. How can I do it?
Edit:
So i know that there is ManyRelatedManager. One thing remain how can i overwrite it?
Sorry... not overwrite, but assign it in my Model by default.
http://docs.djangoproject.com/en/1.2/topics/db/managers/#custom-managers
You can create any number of managers for a Model.
You can subclass a ManyRelatedManager and assign it to the Model.
This example may be what you're looking for
# Then hook it into the Book model explicitly.
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = models.Manager() # The default manager.
dahl_objects = DahlBookManager() # The Dahl-specific manager.
The objects manage is the default. Do not change this.
The dahl_objects is a customized manager. You can have any number of these.

Categories