Django Modelform setting minimum Value on FloatField - python

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'})

Related

Django: How to Auto-Populate field with a related-model's field

I have two basic models:
class ModelA(models.model):
... #some fields
score = models.IntegerField()
class ModelB(models.model)
... #some fields
related_model=models.OneToOneField(ModelA)
score = models.IntegerField(default=related_model.score)
What I want is that upon creation of ModelB, it's score field be filled with the value of score of ModelA to which it has a OneToOne relation.
I have tried setting the score = models.IntegerField(default=related_model.score) but upon migration I get the error:AttributeError: 'OneToOneField' object has no attribute 'score'
I also tried defining a method under ModelB as follows and passing it to the default:
def get_score(self, *args, **kwargs):
return self.threat.score
This doesn't work either.
when I set default=get_score() I get the error: missing one required positional argument: self
How can I automatically set a model's field to be a field of it's related model's (by OneToOne Relation) field?
You should do this on save.
def save(self, *args, **kwargs):
if not self.score:
self.score = self.threat.score
return super().save(*args, **kwargs)

Arithmetic operations between Django fields belonging to the same class model. Is it possible?

I have the need to divide a class field (Value) in Django-admin model with a fixed value (coefficient) in the same class.
The result of this operation should populate another field (Points) of the same class. Both values are of same type (integers).
For example a user enter a value of '180', then he leaves coefficient to its default '10'. When it saves the new entry it should show up Points = 18
So for the moment I defined 'coefficient' field in Django models.py which defaults to 10.
'Value' field is editable as I said above.
I thought to use F() to perform math operations between fields however I am not sure if this is the correct tool or there's something simpler.
When I set up the following expression on my model I see alot complaints when I make db migrations.
Points = Visits.objects.all().annotate(div=F('Value') / F('Coefficient'))
Since I am new to Django I appreciate any help on this, maybe I am misunderstanding something obvious.
Method-1
Override save() method of Value model, as
class Visits(models.Model):
value = models.IntegerField()
coefficient = models.IntegerField()
points = models.IntegerField(blank=True, null=True, editable=False)
def save(self, *args, **kwargs):
self.points = int(self.value / self.coefficient)
super().save(*args, **kwargs)
Method-2
use #property decorator
class Visits(models.Model):
value = models.IntegerField()
coefficient = models.IntegerField()
#property
def points(self):
return int(self.value / self.coefficient)
You can simply override the save() method of your model to calculate the Points field:
def save(self, *args, **kwargs):
self.Points = self.Value / self.Coefficient
super().save(*args, **kwargs)
You might also want to check for and handle division by zero here.

Django: Get previous value in clean() method

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.

Django Form field initial value when updating an instance

I have a custom Django ModelForm that I use to update a model instance.
This is the example model:
class MyModel(models.Model):
number = models.CharField(_("Number"), max_length=30, unique=True)
sent_date = models.DateField(_('Sent date'), null=True, blank=True)
When creating an instance I will pass only the number field, that is why I don't want the sent_date to be required.
Then I have a view that updates the sent_date field, using this custom form:
# Generic form updater
class MyModelUpdateForm(forms.ModelForm):
class Meta:
model = MyModel
fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make fields mandatory
if hasattr(self, 'required_fields'):
for field_name in self.required_fields:
self.fields[field_name].required = True
# Set initial values
if hasattr(self, 'initial_values'):
for field_name, value in self.initial_values.items():
self.initial[field_name] = value
class SentForm(MyModelUpdateForm):
required_fields = ['sent_date']
initial_values = {'sent_date': datetime.date.today()}
class Meta(MyModelUpdateForm.Meta):
fields = ['sent_date']
field_classes = {'sent_date': MyCustomDateField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
MyModelUpdateForm is a generic ancestor for concrete forms like SentForm.
In my view whenever there is a GET I manually instantiate the form with:
my_form = SentForm({instance: my_model_instance})
So in this case I would expect the sent_date field to have an initial value set to today's date even tough the real model instance field is None.
If I inspect my_form object it does indeed have these attributes:
initial: {'sent_date': datetime.date(2018, 3, 1)}
instance: my_model_instance
fields: {'sent_date':
...: ...,
'initial': None # Why this is None?
...: ...
}
So apparently it should work but it doesn't: the field is always empty.
So I suspect that the value is coming from my_model_instance.sent_date that is in fact None.
The initial['sent_date'] = datetime.date(2018, 3, 1) is correct.
On the other side fields['sent_date']['initial'] = None it's not.
How can I always show the initial value when my_model_instance.sent_date is None?
Apparently I've solved with:
class MyModelUpdateForm(forms.ModelForm):
class Meta:
model = MyModel
fields = []
def __init__(self, *args, **kwargs):
initial = kwargs.get('initial', {})
if hasattr(self, 'initial_values') and not kwargs.get('data'):
for field_name, value in self.initial_values.items():
if not getattr(kwargs.get('instance', None), field_name, None):
initial[field_name] = value
kwargs.update({'initial': initial})
super().__init__(*args, **kwargs)
# Make fields mandatory
if hasattr(self, 'required_fields'):
for field_name in self.required_fields:
self.fields[field_name].required = True
Even tough it works I wouldn't mind a less hackish solution if anyone has any :)
I have this case in many places in my app without having any problem. However, I use a different way to set up initial value of some fields of an existing instance. Instead of:
self.initial[field_name] = value
I write, after having called super():
self.fields[field_name].initial = value
Can you try and tell the result ?

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

Categories

Resources