Django: Get previous value in clean() method - python

I have a model CustomModel with an IntegerField.
class CustomModel(models.Model):
count = models.IntegerField()
When I create a new instance of CustomModel in the admin, I have to do validation, so I use the clean method and have access to the value with.
def clean(self):
value = self.count
...
My problem:
When I change the instance of CustomModel, I only have access to the new, changed value but not to the original value. However, for my validation I have to compare the new value and the value before the instance got edited.
I could not found a solution how to get access. Does somebody know?

Why not take advantage of a ModelForm? Form data is saved in two steps:
To the form instance
To the model instance
So when you have a form:
class YourForm(forms.ModelForm):
class Meta:
model = CustomModel
fields = ['count']
def clean(self):
cleaned_data = super().clean()
count = cleaned_data.get('count')
if count < self.instance.count:
self.add_error('count', 'You cannot decrease the counter')
return cleanded_data
You can then override the form within the django admin site.

There's also a solution just using the model:
def clean(self):
if self.pk:
previous_count = self.__class__.objects.get(pk=self.pk).count
else:
previous_count = None # If saving a new instance
self.__class__ access the model class and fetch the currently stored .count value.

Related

how to overwrite save method in django model form

i'm trying to overwrite save method in my forms.py ,i have to prevent from creating duplicated objects , and if the object exists only update some fields
class Item(models.Model):
item = models.ForeignKey(Product,on_delete=models.CASCADE)
quantity = models.IntegerField()
for example if i entered this data before : item = XYZ , quantity = 100 i want to prevent from creating another XYZ item , i want to just update the quantity , for example i'll enter this data item = XYZ , quantity = 200 i try to prevent from creating this duplicate data , i just try to update the quantity previous quantity + new quantity 100 + 200 = 300 i must update the quantity to 300 for that purpose i overwrite save() in my forms.py
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields = ['item','quantity']
def save(self,*args,**kwargs):
if self.instance.item is None: #i also tried this if not self.instance.item
return super().save(*args,**kwargs)
else:
Item.objects.filter(item__name=self.instance.item).update(
quantity=F('quantity') + self.instance.quantity)
my views.py
def createNewProduct(request):
form = ItemForm()
if request.method == 'POST':
form = ItemForm(request.POST)
if form.is_valid():
form.save()
return render(request,'temp/add_item.html',{'form':form})
but it only update if it exists if not exists it doesn't create any new object , iexpect to create new object if it didn't exists , isn't there any way to achieve it please ? or i didn't something wrong ?
This is How I usually overwrite save method in model form:
def save(self, commit=True):
# your logic or Save your object for example:
obj = Model.objects.create(...)
return obj
Or you can also do this:
def save(self, commit=True):
obj = super().save(commit=False)
# do you logic here for example:
obj.field = something
if commit:
# Saving your obj
obj.save()
return obj
According to the documentation for ModelForm.save():
A subclass of ModelForm can accept an existing model instance as the
keyword argument instance; if this is supplied, save() will update
that instance. If it’s not supplied, save() will create a new instance
of the specified model.
This means that in your createNewProduct view, when handling POST requests, you need to check whether an Item already exists in the database and if so pass it to the Form constructor for editing, otherwise instantiate the ModelForm as per usual to create a new Item. So actually there's no need to override the ModelForm's save method
Since you want to add the old and new quantities instead of overwriting them you need to take care of that before the form is saved. This should typically happen in the form's clean method.
The resulting ItemForm and createNewProduct view would then look like this:
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields = ['item','quantity']
def clean(self):
cleaned_data = super().clean()
# if this Item already exists
if self.instance:
# add the old quantity to the new quantity
cleaned_data['quantity'] += self.instance.quantity
return cleaned_data
def createNewProduct(request):
if request.method == 'POST':
try:
dbItem = Item.objects.get(item=request.POST['item'])
except Item.DoesNotExist:
# get form for new Item
form = ItemForm(request.POST)
else:
# get form for existing Item
form = ItemForm(request.POST,instance=dbItem)
finally:
if form.is_valid():
form.save()
return redirect('success') # redirect on success
return redirect('failure') #redirect on failure
else:
form = ItemForm()
return render(request,'temp/add_item.html',{'form':form})

How get instance in a method clean from some forms

How can I get the Article instance in my model form's clean method? I tried too access self.instance but it is None. How do I get the previous field values?
model
class Article(models.Model):
name = models.CharField(max_length=25)
value = models.CharField(max_length=25)
forms
class ArticleForm(forms.ModelForm)
class Meta:
model = Article
fields = '__all__'
def clean(self):
cleaned_data = super().clean()
get_instance = self.instance
print(get_instance) and I get None
views
def test(request)
form = ArticleForm({'name':'test', 'value':'test'})
if form.is_valid():
print(1)
else:
print(form.errors)
You get None because you didn't instantiate the form with an instance.
form = ArticleForm({'name':'test', 'value':'test'})
If you instantiate the form with an instance, then you can access it with self.instance in the clean method.
article = Article.objects.get(pk=1)
form = ArticleForm({'name':'test', 'value':'test'}, instance=article)
However, note that cleaning the form alters the model instance. If you want the original values, you should refetch the instance from the database, e.g. original_instance = Art
def clean(self):
cleaned_data = super().clean()
if self.instance is not None and self.instance.pk is not None:
original_instance = Article.objects.get(pk=self.instance.pk)
else:
original_instance = None
...
If you only want to know which fields changed, and don't care about their original values, it would be simpler to use the changed_data attribute.

Django Modelform setting minimum Value on FloatField

i am trying to set the min_value Attribute on Form level within my modelform.
Forms.py
class ProductForm(forms.models.ModelForm):
class Meta:
model = Artikel
localized_fields = '__all__'
fields = ('price',)
Model.py
class Artikel(models.Model):
price = models.FloatField(help_text ='Price')
How can i setup the modelform that i can constrain the values allowed on the modelform?
I want the user to only enter values greater than or equal to 0.01.
I dont want to restricted on Database Level cause i dont want to limit myself in that regard.
You can override the ModelForm's init method. This will set the min attribute on the field to 10:
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['price'].widget.attrs['min'] = 10
In addition to setting 'min' attribute on widget, also override form's clean_fieldname() method:
class ProductForm(forms.models.ModelForm):
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
self.fields['price'].widget.attrs['min'] = 0.01
def clean_price(self):
price = self.cleaned_data['price']
if price < 0.01:
raise forms.ValidationError("Price cannot be less than 0.01")
return price
class Meta:
model = Artikel
localized_fields = '__all__'
fields = ('price',)
Doc says:
The clean_<fieldname>() method is called on a form subclass – where is replaced with the name of the form field attribute. This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is. This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).
The simple way to do this is to set the validator on the field and provide a custom error message:
class ProductModelForm(forms.ModelForm):
price = forms.FloatField(min_value=0.01,
error_messages={'min_value': u'Price cannot be less than 0.01'})

Django admin removes selected choice in ModelChoiceField on edit?

I'm using admin.TabularInline in my admin code for which I've made a custom form.
class RateCardForm(forms.ModelForm):
category = forms.ModelChoiceField(queryset=models.Category.objects.all(), label='Category')
class Meta:
model = models.RateCard
fields = ('category')
class RateCardInline(admin.TabularInline):
model = models.RateCard
form = RateCardForm
extra = 3
The problem is that after I've saved my model instance, whenever I edit the model instance, it would remove the pre-selected choice and I'll have to select the choice again. Any ideas as to how to stop it?
Also for ModelChoiceField if I don't specify the label, then it would come up as None on admin page, but I don't need to specify it for admin.StackedInline.
To preselect the currently selected category instance you can set its primary key to the field's initial value by overriding __init__() on the ModelForm:
class RateCardForm(forms.ModelForm):
category = forms.ModelChoiceField(queryset=models.Category.objects.all(), label='Category')
class Meta:
model = models.RateCard
fields = ('category')
def __init__(self, *args, **kwargs):
super(RateCardForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance')
# Instance will be None for the empty extra rows.
if instance:
selected_pk = # query the primary key of the currently selected category here
self.fields['category'].initial = selected_pk

django rest framework and model inheritance

I have an "abstract" model class MyField:
class MyField(models.Model):
name = models.CharField(db_index = True, max_length=100)
user = models.ForeignKey("AppUser", null=False)
I have a few other subclasses of MyField each defining a value of a specific type.
for example:
class MyBooleanField(MyField):
value = models.BooleanField(db_index = True, default=False)
In MyField I have a method get_value() that returns the value based on the specific subclass.
In django rest I want to fetch all the fields of a user
class AppUserSerializer(serializers.ModelSerializer):
appuserfield_set = MyFieldSerializer(many=True)
class Meta:
model = AppUser
fields = ('appuser_id', 'appuserfield_set')
On the client side I want the user to be able to add new fields and set values to them and then on the server I want to be able to create the correct field based on the value.
What is the correct way to achieve this behavior?
After some digging, here is what I ended up doing. Aside from the code below I had to implement get_or_create and create the relevant subclass of MyField based on the passed value.
class ValueField(serializers.WritableField):
#called when serializing a field to a string. (for example when calling seralizer.data)
def to_native(self, obj):
return obj;
"""
Called when deserializing a field from a string
(for example when calling is_valid which calles restore_object)
"""
def from_native(self, data):
return data
class MyFieldSerializer(serializers.ModelSerializer):
value = ValueField(source='get_value', required=False)
def restore_object(self, attrs, instance=None):
"""
Called by is_valid (before calling save)
Create or update a new instance, given a dictionary
of deserialized field values.
Note that if we don't define this method, then deserializing
data will simply return a dictionary of items.
"""
if instance:
# Update existing instance
instance.user = attrs.get('user', instance.user)
instance.name = attrs.get('name', instance.name)
else:
# Create new instance
instance = MyField.get_or_create(end_user=attrs['user'],
name=attrs['name'],
value=attrs['get_value'])[0]
instance.value = attrs['get_value']
return instance
def save_object(self, obj, **kwargs):
#called when saving the instance to the DB
instance = MyField.get_or_create(end_user=obj.user,
name=obj.name,
value=obj.value)[0]
class Meta:
model = MyField
fields = ('id', 'user', 'name', 'value')

Categories

Resources