Django - Replace model fields values before save - django

Just to note: I know about overwriting Model's .save() method but it won't suit me as I explained it at the end of my question.
In one of my projects, I've got more than of 30 database Models and each one of these models accept multiple CharField to store store Persian characters; However there might be some case where users are using Arabic layouts for their keyboard where some chars are differ from Persian.
For example:
The name Ali in Arabic: علي
and in Persian: علی
Or even in numbers:
345 in Arabic: ٣٤٥
And in Persian: ۳۴۵
I need to selectively, choose a set of these fields and run a function on their value before saving them (On create or update) to map these characters so I won't end up with two different form of a single word in my database.
One way to do this is overwriting the .save() method on the database Model. Is there any other way to do this so I don't have to change all my models?

Sounds like a good use of Django's Signals. With Signals you can call a function before or after save on one or more Models.
https://docs.djangoproject.com/en/3.2/topics/signals/
Django includes a “signal dispatcher” which helps decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place. They’re especially useful when many pieces of code may be interested in the same events.
django.db.models.signals.pre_save & django.db.models.signals.post_save
Sent before or after a model’s save() method is called.
https://docs.djangoproject.com/en/3.2/ref/signals/#pre-save
pre_save
django.db.models.signals.pre_save
This is sent at the beginning of a model’s save() method.
Arguments sent with this signal:
sender
The model class.
instance
The actual instance being saved.
raw
A boolean; True if the model is saved exactly as presented (i.e. when loading a fixture). One should not query/modify other records in the database as the database might not be in a consistent state yet.
using
The database alias being used.
update_fields
The set of fields to update as passed to Model.save(), or None if update_fields wasn’t passed to save().
You can connect signals to a Single, Multiple, or All models in your application.
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
#receiver(pre_save)
def my_handler(sender, instance, **kwargs):
# logic here
In this case sender is the Model that is about to be saved, instance will be the object being saved.

You can create a completely custom model field however in your case it's really easier to just customize Django's CharField:
class MyCharField(models.CharField):
def to_python(self, value):
if value is None or value == "":
return value # Prevent None values being converted to "None"
if isinstance(value, str):
return self.func_to_call(value)
return self.func_to_call(str(value))
Replace your models CharField with MyCharField.
And create the .func_to_call() so it does whatever you need to map the values:
def func_to_call(self, value):
# Do whatever you want to map the values
return value

Related

Django custom validation before the data is saved (Enforce at the database level)

This is an extension from my post here preventing crud operations on django model
A short into to the problem , im currently using a package called django-river to implement a workflow system in my application. The issue is that they do not have a predefined 'start' , 'dropped' , 'completed' state. Their states are stored as a django model instance. This would mean that my application is unable to programmatically differentiate between the states. Therefore , the labels of these states has to be hardcoded into my program (Or does it? Maybe someone has a solution to this?)
Suppose that there is no solution to the issue other than hardcoding the states into my application , this would mean that i would have to prevent users from updating , or deleting these states that i have pre created initially.
My idea is to have a form of validation check within the django model's save method . This check would check that the first 3 instances of the State model is always start , deactivated and completed and in the same order. This would prevent the check from passing through whenever a user trys to change items at the ORM level.
However , it would seem that there is 2 issues with this:
I believe django admin doesn't run the model class save method
Someone is still able to change the states as long as the way they changed it does not pass through the save() method. AKA from the DB SQL commands
Although it is unlikely to happen , changing the name would 'break' my application and therefore i wish to be very sure that no one can edit and change these 3 predefined states.
Is there a fool proof way to do this?
My idea is to have a form of validation check within the django model's save method.
if i understand your description, maybe you can just override the save() function of your model like so:
class MyModel(models.Model):
[..]
def save(self, *args, **kwargs):
# Put your logic here ..
super(MyModel, self).save(*args, **kwargs)
I got the answer from django documentation
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
You can add this to a model field via the field’s validators argument:
from django.db import models
class MyModel(models.Model):
even_field = models.IntegerField(validators=[validate_even])
FYI: It is not really mandatory to use gettext_lazy and you can use just message as follows
from django.core.exceptions import ValidationError
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
('%(value)s is not an even number'),
params={'value': value},
)

Django models TextField/CharField: Remove null characters

I have a view that takes, in many parts, data from a, let's say, untrusted source, and I want to save the data in a Django model's TextField. The input may have null characters, and, if I understand correctly, Postgres (the backend of my project) prohibits saving null characters in the database. I could use str.replace to replace the null characters, but I would have to riddle my codebase with str.replace, since I have many points where I create models out of the untrusted data. Is there a way to force the cleaning of the data in the model level? I mean, so that I could do my_model.save() and in a unique place in my code have str.replace, instead of having it in all the places I have a TextField.
You can do that by writing a post save signal, in the following way:
https://docs.djangoproject.com/en/1.11/ref/signals/#post-save
This is an example implementation:
from django.db.models.signals import post_save
from django.dispatch import receiver
class SampleModel(models.Model):
# ... fields here
# method for your validations
#receiver(post_save, sender=SampleModel)
def validate_input(sender, instance, **kwargs):
# your custom validations
instance.save()

Django form save with post_save signal causing conflict

I have a Physical_therapy_order model and an Event model (an event has foreignkey to Physical_therapy_order). I have a view which allows a user to create a new event. It also has a form with 3 fields from the Physical_therapy_order model.
def PTEventCreateView(request, pt_pk):
#get the pt order and create an a form for that order
pt_order = get_object_or_404(Physical_therapy_order, pk=pt_pk)
ptform = PT_schedule_form(instance=pt_order)
if request.POST:
eventform = PTEventForm(data=request.POST)
ptform = PT_schedule_form(data=request.POST, instance=pt_order)
if eventform.is_valid() and ptform.is_valid():
#I do some checks here that compare data across the two forms.
# if everything looks good i mark keep_saving=True so I can
# continue to save all the data provided in the two forms
if keep_saving:
ptform.save()
eventform.save()
#...send user to succss page
This works just FINE EXCEPT: my PTEvent model has a function attached to its post_save signal. This function pulls the event's related pt_order and makes some modifications to it. Now, if i save the eventform first then the changes from the signal don't happen. if i save the ptform first the ptform changes get discarded and the changes from the signal happen.
THIS IS IMPORTANT: The ptform is editing three entirely different fields than the post_save signal. So its not like they're modifying the same data, only the same model instance. I thought a form only saves the fields in its meta.fields attribute. Why would this be happening? Also, if i save the ptform first, then when eventsform is saved shouldn't the signal use the updated physical_therapy_order? I'm not sure if I'm even on the right track?
I think this is because of cached objects.
What I would suggest is
Save eventform first
Get new instance of pt_order either querying db or through saved instance of eventform
And then re-create form and save.
Sample code change:
# your code
if keep_saving:
evt = eventform.save()
# I'm not sure exact name of your field name for pt_order in Event model, change appropriately
newptform = PT_schedule_form(data=request.POST, instance= evt.pt_order)
newpt = newptform.save()

Form/ModelForm instances between requests

I want write a custom form field (and possibly widget too) and I'm not sure about how the form instances are shared between requests. For example, if I render a form with data from a model instance, is that instance still available when I am validating data? If so, does that mean that there is another database hit to look up the model again between requests?
Similarly, if I write a custom field that takes in a list of data to display in its __init__ method, will that list of data be available to validate against when the user POSTs the data?
It would be really helpful if someone could point me to parts of the django source where this occurs. I've been looking at the models.py, forms.py, fields.py and widgets.py from django.forms, but I'm still not 100% sure how it all works out.
Eventually, what I want to do is have a field that works something like this (the key part is the last line):
class CustomField(ChoiceField):
def __init__(self, data_dict, **kwargs):
super(CustomField, self).__init__(**kwargs)
self.data_dict = data_dict
self.choices = data_dict.keys()
def validate(self, value):
if value not in self.data_dict:
raise ValidationError("Invalid choice")
else:
return self.data_dict[value]
Will that data_dict be available on the next request? If I create a custom forms.Form and initialize it with the data_dict, will that be available on the next request? (e.g. with a factory method or something...).
Side note: I'm doing this because I want to (eventually) use something like Bootstrap's typeahead and I'd like to pass it "pretty values" which I then convert server-side (basically, like how option values in a select can have a different submitted value). I've done this with client-side javascript in the past, but it would be nice to consolidate it all into a form field.
There's nothing magical about forms. Like everything else in Django (or just about any web framework), objects don't persist between requests, and need to be reinstantiated each time. This happens in the normal view pattern for form handling: you instantiate it once for a POST, and a separate time for a GET. If you have data associated with the form, it would need to be passed in each time.

In Django, how do I get a signal for when a Group has a User added or removed?

In the Django admin I sometimes add or delete users to or from (existing) groups. When this happens I'd like to be able to run a function.
I'm just using the standard User and Group models.
I have looked at doing it with signals, through m2m_changed, but it seems to need a Through class - and I don't think there is one in this case.
From the django doc:
sender - The intermediate model class describing the ManyToManyField. This class is automatically created when a many-to-many field is defined; you can access it using the through attribute on the many-to-many field.
When subscribing to m2m_changed like so:
#receiver(m2m_changed)
def my_receiver(**kwargs):
from pprint import pprint
pprint(kwargs)
You will receive a bunch of signals like this (shortened):
{'sender': <class 'django.contrib.auth.models.User_groups'>,
'action': 'post_add',
'instance': <User: bouke>,
'model': <class 'django.contrib.auth.models.Group'>,
'pk_set': set([1]),
'reverse': False,
'signal': <django.dispatch.dispatcher.Signal object at 0x101840210>,
'using': 'default'}
So the user bouke has been added to pk_set groups: [1]. However I noted that the admin layout clears all groups and then adds the selected groups back in. The signals you will receive are pre_clear, post_clear, pre_add, post_add. Using a combination of these signals you could store the pre and post groups. Doing a diff over these lists, you have the deleted and added groups for the user.
Note that the signals are the other way around (pk_set and instance) when editing a group instead of a user.
You'll see in the Django documentation (v1.11) that your desired sender should be the intermediate through field belonging to the ManyToMany field, wherever that's defined. If you register that as your sender, then you'll be listening to eg Users adding Groups to themselves, as well as Groups adding Users to themselves.
self.walrus.groups.remove(self.peon_group)
#receiver(signal=m2m_changed, sender=User.groups.through)
def adjust_group_notifications(instance, action, reverse, model, pk_set, using, *args, **kwargs):
if model == Group and not reverse:
logger.info("User %s deleted their relation to groups «%s»", instance.username, pk_set)
if action == 'post_remove':
# The *modification* happening is a deletion of the link
…
elif action == 'post_add':
logger.info("User %s created a relation to groups «%s»", instance.username, ", ".join(pk_set))
…
else:
logger.info("Group %s is modifying its relation to users «%s»", instance, pk_set)
…
return
You need to create a signal using m2m_changed as a receiver. According to the official Django documentation:
A signal is sent when a ManyToManyField is changed on a model instance. Strictly speaking, this is not a model signal since it is sent by the ManyToManyField.
So, the simplest implementation is as follows:
#receiver(m2m_changed)
def signal_handler(**kwargs):
from pprint import pprint
pprint(kwargs)
In your case, you want to perform something when a user is added or removed from a group, so you can take advantage of the action parameter when it takes the values 'pre_add', 'post_add', 'pre_remove', and 'post_remove'. You can also take advantage of pk_set parameter which contains primary key values that have been added to or removed from the relation.
#receiver(m2m_changed)
def signal_handler_when_user_is_added_or_removed_from_group(action, instance, pk_set, model, **kwargs):
if model == Group:
if action == 'pre_add':
# TODO: add logic
pass
elif action == 'post_add':
# TODO: add logic
pass
# handle as many actions as one needs
# The following for loop prints every group that were
# added/removed.
for pk in pk_set:
group = Group.objects.get(id=pk)
print(group)
It might be better to try and achieve this with django-celery, that way you can write custom tasks, and based on a certain criteria (such as removal or addition) you can fire of a certain task.
Celery
RabbitMQ