Making a django model field readonly - python

I am creating a django DB model and I want one the fields to be readonly. When creating a new object I want to set it, but later if someone tries to update the object, it should raise an error. How do I achieve that?
I tried the following but I was still able to update the objects.
from django.db import models as django_db_models
class BalanceHoldAmounts(django_db_models.Model):
read_only_field = django_db_models.DecimalField(editable=False)
Thank you

You can override it in the "save" method of the model and raise a Validation Error if someone tries to update that field.
def save(self, *args, **kwargs):
if self.pk:
previous_value = BalanceHoldAmounts.objects.get(pk=self.pk)
if previous_value.read_only_field != self.read_only_field:
raise ValidationError("The read_only_field can not be changed")
super().save(*args, **kwargs)

Related

django - disable Invitation object creation if User object with the email exists

I'd like to make sure that nobody can't create an Invitation object with an email that is already in a database either as Invitation.email or as User.email.
To disallow creating Invitation with existing Invitation.email is easy:
class Invitation(..):
email = ...unique=True)
Is it also possible to check for the email in User table? I want to do this on a database or model level instead of checking it in serializer, forms etc..
I was thinking about UniqueConstraint but I don't know how to make the User.objects.filter(email=email).exists() lookup there.
You can override the save() method on the model, and check first in the users table. You should look that is a new model. Something like this I think:
class Invitation(..):
email = ...unique=True)
def save(self, *args, **kwargs):
if self.id is None and User.objects.filter(email=self.email).exists():
raise ValidationError('Email already used.')
else:
super().save(*args, **kwargs)
You can do it in the model.. as below. Or you can do it in the database with a Check Constraint (assuming postgres).. but you still can't avoid adding code to your view, because you'll need to catch the exception and display a message to the user.
class Invitation(models.Model):
def save(self, *args, **kwargs):
if (not self.pk) and User.objects.filter(email=self.email).exists():
raise ValueError('Cannot create invitation for existing user %s.' % self.email)
return super().save(*args, **kwargs)
PS: Some may ask why it is that I am passing *args and **kwargs to the superclass, or returning the return value.. when save has no return value. The reason for this is that I never assume that the arguments or return value for a method I am overriding won't change in the future. Passing them all through if you have no reason to intercept them, is just a good practice.
How about overriding the save method?
class Invitation(...):
...
def save(self, *args, **kwargs):
# check if an invitation email on the user table:
if User.objects.get(id=<the-id>).email:
# raise integrity error:
...
# otherwise save as normal:
else:
super().save(*args, **kwargs)

How should i auto fill a field and make it readonly in django?

I`m new to django and i was doing a test for my knowledge.
Found a lot of duplicates in here and web but nothing useful
I'm trying to make a ForeignKey field which gets filled due to the other fields that user fills, and make it unchangeable for the user.
I thought that I should use overriding save() method but couldn't figure that at all.
How should I do that auto-fill and read-only thing?
Your approach is right. Override the save method and if self.pk is not None raise an exception if your field has changed. You can use django model utils to easily track changes in your model: https://django-model-utils.readthedocs.io/en/latest/utilities.html#field-tracker
Principle:
class MyModel(models.Model):
#....
some_field = models.Foreignkey(...)
tracker = FieldTracker()
def save(*args, **kwargs):
if self.pk is None:
# new object is being created
self.some_field = SomeForeignKeyObject
else:
if self.tracker.has_changed("some_field"):
raise Exception("Change is not allowed")
super().save(*args, **kwargs)

Django EventLog: Passing in current user

I am trying to add logging to my Django app using EventLog. I followed an example online but not sure how to pass in the user that makes the changes. The example shows it as user=self.user. Obviously this wouldn't work in my case as it doesn't refer to anything in my model
models.py
class Client(models.Model):
name = models.CharField(max_length=50)
....
def save(self, *args, **kwargs):
# Initial Save
if not self.pk:
log(user=self.user, action='ADD_CLIENT',
extra={'id': self.id})
else:
log(user=self.user, action='UPDATED_CLIENT',
extra={'id': self.id})
super(Client, self).save(*args, **kwargs)
The save method will only know what has been passed into it, this will normally not include the request which is where you would get the current user (request.user).
You should instead add logging in the view which is calling the save method.
user = request.user

How can I know which values were changed in Django's Model.save() method?

I have overwritten the Django's Model.save() method in order to perform manipulations on an external database when an object is added or updated.
Basically, here's my method:
def save(self, *args, **kwargs):
if self.pk is None:
# Insert query on the external database.
else:
# Update query on the external database.
super(MyModel, self).save(*args, **kwargs)
My question is now, I know I can access the data submitted just by doing self.name for example, but how can I access the old data? I mean, the existing data, in the case of an update of course.
Because here's my problem, the external database doesn't support lots of queries and I want to do the query only if the field in question was updated.
Here is what I would like to do:
def save(self, *args, **kwargs):
if self.pk is None:
# Insert query on the external database.
else:
if self.name is not self.THE_CURRENT_NAME
# Update query on the external database.
super(MyModel, self).save(*args, **kwargs)
Anyone has an idea?
If you're using ModelForm for the job, override save method on it and decide whether to cascade to model's save (call super with commit=True) or not.
You probably have all the information at this point. Now you need a way to use it.
Forms in django have tools for what you need. Check out changed_data attribute and has_changed() method of the form.
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def save(commit=True):
if self.has_changed():
super(MyModelForm, self).save(commit=commit)
You will have to fetch it from the DB before the saving:
def save(self, *args, **kwargs):
if self.pk is None:
# Insert query on the external database.
else:
old = Lab.objects.get(pk=self.pk)
if self.name is not old.name:
...
super(Lab, self).save(*args, **kwargs)
so you want to get your data from the database, and you don't want to make many queries. If your data is not too much, you can just read all the data from your database to the memory, and then compare your about-to-save value with the data in memory, if they are not the same, just assign that value to the memory data as well as updating in the database.

Raising ValidationError from django model's save method?

I need to raise an exception in a model's save method. I'm hoping that an exception exists that will be caught by any django ModelForm that uses this model including the admin forms.
I tried raising django.forms.ValidationError, but this seems to be uncaught by the admin forms. The model makes a remote procedure call at save time, and it's not known until this call if the input is valid.
Thanks, Pete
Since Django 1.2, this is what I've been doing:
class MyModel(models.Model):
<...model fields...>
def clean(self, *args, **kwargs):
if <some constraint not met>:
raise ValidationError('You have not met a constraint!')
super(MyModel, self).clean(*args, **kwargs)
def full_clean(self, *args, **kwargs):
return self.clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(MyModel, self).save(*args, **kwargs)
This has the benefit of working both inside and outside of admin.
There's currently no way of performing validation in model save methods. This is however being developed, as a separate model-validation branch, and should be merged into trunk in the next few months.
In the meantime, you need to do the validation at the form level. It's quite simple to create a ModelForm subclass with a clean() method which does your remote call and raises the exception accordingly, and use this both in the admin and as the basis for your other forms.

Categories