How can I implement a verified field in django? - python

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)

Related

Django: What is the simplest way to asynchronously execute some function when there is a change to the database?

I am familiar wıth the DBMS_ALERT feature in Oracle that sends an alert to the Operating System when there is a change to the database, and am somewhat familiar with database triggers in general. I have browsed through the django docs on signals, and inclined to use them if there is not a simpler way.
All I want to do is update or create an external file on the systems file system whenever there is an update or created record in the database. I would like this method to be called and defined right in models.py as depicted below.
models.py
from django.db import models
from django.shortcuts import reverse
class Entry(models.Model):
subject = CharField(max_length=20, unique=True)
content = models.TextField(blank=False)
class Meta:
ordering = ['subject']
def __str__(self):
"""String for representing the Model object."""
return self.subject
def get_absolute_url(self):
"""Returns the url to access a detail record for this entry."""
return reverse('entry_detail', args=[int(self.id)])
def ondbchange_updatecorrfile(self):
# this method would be called upon change in the model Entry, so it is already aware of object
util.save_entry(self.subject,self.content) # file corresponding to row is updated or created
What would be the simplest method to implement the ondbchange_updatecorrfile(self) method above?
I have looked at the below code from this source
models.py
class Entry(models.Model):
...
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Entry)
def queue_task(sender, instance, created, **kwargs):
tasks.foo.delay(object=instance)
Then foo is some function in another class that would update the file. Since the database Model is the class that is aware of the change in the Model's underlying database, do we really need to use a signal if the desired function is already in the model and "self aware" of the database change?
Any help deeply appreciated. Please be specific since the devil is in the details.
The post_save signal is sent by the model Entry after a save, but according to this source, the signal does not include the model changes in its update_fields parameter. These must be manually inserted in the override of the function where the save() happens; which defeats the purpose. I initially assumed that the signal would automatically include this information.

Related models update on model field change

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.

In Django, how can I allow 'null' values in a field programatically from the form?

I am actually programing an application in django and I have the next model:
class A(models.Model):
att1= models.ForeignKey(B, related_name="att1")
att2= models.ForeignKey(C)
...
As you can see, the attributes of the model cannot be 'null'.
I also have a search-form of the model above, and, when I treat it in the correspondent view, I have the next error in this code line:
# If the two forms are valid...
if A_form.is_valid():
Error -> Cannot assign None: "A.att1" does not allow null values.
So that I supose the error occurs because in the model I didn´t define null=True for that attribute.
Well, the problem is that when I create an object referenced to de model A, I want the attributes 'att1' and 'att2' not to be 'null', but in the search form it doesn't matter if the fields are filled. Of course, in the form, I defined that the attributes are not required with required=False:
class Aform(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(Aform, self).__init__(*args, **kwargs)
# Making name required
self.fields['att1'].required = False
self.fields['att2'].required = False
So, my question is: how can I fix this error? Can I attach "null=True" option to the attributes in the form, as well as I could do in the model definition? Is there any way to allow the attributes be null in the search form?
Thank you so much!
Assuming that Aform is not overriding directly forms.ModelForm, you can override is_valid or clean_att1
Doc of clean_att1 here

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?

Why doesn't django's model.save() call full_clean()?

I'm just curious if anyone knows if there's good reason why django's orm doesn't call 'full_clean' on a model unless it is being saved as part of a model form.
Note that full_clean() will not be called automatically when you call your model’s save() method. You’ll need to call it manually when you want to run one-step model validation for your own manually created models.
django's full clean doc
(NOTE: quote updated for Django 1.6... previous django docs had a caveat about ModelForms as well.)
Are there good reasons why people wouldn't want this behavior? I'd think if you took the time to add validation to a model, you'd want that validation run every time the model is saved.
I know how to get everything to work properly, I'm just looking for an explanation.
AFAIK, this is because of backwards compatibility. There are also problems with ModelForms with excluded fields, models with default values, pre_save() signals, etc.
Sources you might be intrested in:
http://code.djangoproject.com/ticket/13100
http://groups.google.com/group/django-developers/browse_frm/thread/b888734b05878f87
Because of the compatibility considering, the auto clean on save is not enabled in django kernel.
If we are starting a new project and want the default save method on Model could clean automatically, we can use the following signal to do clean before every model was saved.
from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save
#receiver(pre_save)
def pre_save_handler(sender, instance, *args, **kwargs):
instance.full_clean()
The simplest way to call the full_clean method is just to override the save method in your model:
class YourModel(models.Model):
...
def save(self, *args, **kwargs):
self.full_clean()
return super(YourModel, self).save(*args, **kwargs)
Commenting on #Alfred Huang's answer and coments on it. One might lock the pre_save hook down to an app by defining a list of classes in the current module (models.py) and checking against it in the pre_save hook:
CUSTOM_CLASSES = [obj for name, obj in
inspect.getmembers(sys.modules[__name__])
if inspect.isclass(obj)]
#receiver(pre_save)
def pre_save_handler(sender, instance, **kwargs):
if type(instance) in CUSTOM_CLASSES:
instance.full_clean()
If you have a model that you want to ensure has at least one FK relationship, and you don't want to use null=False because that requires setting a default FK (which would be garbage data), the best way I've come up with is to add custom .clean() and .save() methods. .clean() raises the validation error, and .save() calls the clean. This way the integrity is enforced both from forms and from other calling code, the command line, and tests. Without this, there is (AFAICT) no way to write a test that ensures that a model has a FK relation to a specifically chosen (not default) other model.
class Payer(models.Model):
name = models.CharField(blank=True, max_length=100)
# Nullable, but will enforce FK in clean/save:
payer_group = models.ForeignKey(PayerGroup, null=True, blank=True,)
def clean(self):
# Ensure every Payer is in a PayerGroup (but only via forms)
if not self.payer_group:
raise ValidationError(
{'payer_group': 'Each Payer must belong to a PayerGroup.'})
def save(self, *args, **kwargs):
self.full_clean()
return super().save(*args, **kwargs)
def __str__(self):
return self.name
Instead of inserting a piece of code that declares a receiver, we can use an app as INSTALLED_APPS section in settings.py
INSTALLED_APPS = [
# ...
'django_fullclean',
# your apps here,
]
Before that, you may need to install django-fullclean using PyPI:
pip install django-fullclean
A global pre_save signal can work well if you want to always ensure model validation. However it will run into issues with Django's auth in current versions (3.1.x) and could cause issues with models from other apps you are using.
Elaborating on #Peter Shannon's answer, this version will only validate models inside the module you execute it in, skips validation with "raw" saves and adds a dispatch_uid to avoid duplicate signals.
from django.db.models.signals import pre_save
import inspect
import sys
MODELS = [obj for name, obj in
inspect.getmembers(sys.modules[__name__], inspect.isclass)]
def validate_model(sender, instance, **kwargs):
if 'raw' in kwargs and not kwargs['raw']:
if type(instance) in MODELS:
instance.full_clean()
pre_save.connect(validate_model, dispatch_uid='validate_models')

Categories