django field validator based on other field in same model - python

I have this model
class Env(models.Model):
functional_count = models.PositiveIntegerField()
current_count = models.PositiveIntegerField()
Now i want functional_count to always be less than current_count.
So during create,
def form_valid(self, form):
form.instance.current_count = 0
This is because i want current_count during initialization.Then my python code never allows current_count to go above functional_count.
The problem comes in Update.
class EnvUpdate(UpdateView):
model = Capacity.models.Envapps
fields = ['functional_count']
template_name_suffix = '_update_form'
So do i include a validator? If yes, where and how?
Or other option is to verify in get_success_url().
Any other solution?

Assuming your updates come through a form (as suggested by the use of form_valid(), perhaps you can use the form clean() method, as described in the documentation. This allows you to perform checks for fields that depend on each other. The documentation also has an example which should get you further.
Update
From your comments, I understand that you tried to use clean() inside EnvUpdate, which inherits from the class-based UpdateView view. All the mixins provided through UpdateView do apparently not provide a clean() method, so you can't override it.
I am actually referring to the clean() in a form class (as follows from the link). So, it looks like you'd need to create your own ModelForm class, something like:
class EnvappsForm(forms.ModelForm):
class Meta:
model = Capacity.models.Envapps
fields = ['functional_count']
def clean(self):
cleaned_data = super(ContactForm, self).clean()
if cleaned_data['functional_count'] >= form.instance.current_count:
raise ValidationError('too large')
return cleaned_data
and then in your view:
class EnvUpdate(UpdateView):
model = Capacity.models.Envapps
template_name_suffix = '_update_form'
form_class = EnvappsForm
Note: this is completely untested! I don't know if the comparison in the clean() works (i.e., if form.instance.current_count can be found), and wheter EnvUpdate will override the form_class (it shouldn't, but I've never tried). It just might be even possible that you can remove the meta subclass, and provide the model and fields from EnvUpdate, as you do yourself above. That's just something you can easily try out.

If functional_count should always be less than current_count you should check it in the clean() method on the model and not some random ModelForm. The model clean() will be called during normal ModelForm validation.

Related

Django Rest Framework MultipleChoiceField with dynamic options

So I just started using Django Rest Framework and one of my serializers has a MultipleChoiceField in which the choices are simply all the instances of another model.
Here is the serializer in question:
class ObjectTypeSerializer(serializers.ModelSerializer):
def get_field_choices():
return sorted([
(p.id, p.name) for p in Parameter.objects.all()
])
object_fields = serializers.MultipleChoiceField(
choices=get_field_choices()
)
instance_fields = serializers.MultipleChoiceField(
choices=get_field_choices()
)
labels = serializers.SlugRelatedField(
queryset=Label.objects.all(),
many=True, allow_null=True, slug_field='name'
)
class Meta:
model = ObjectType
fields = ('id', 'name', 'object_fields',
'instance_fields', 'labels')
However, when I add a new Parameter object, the choices are not updated. In regular Django forms, I solved this simply using
forms.ChoiceField(choices=[(p.id, p.name) for p in Parameter.objects.all()])
and it would update the choices when a new parameter is added without restarting the server. How can I accomplish the same thing with Django Rest Framework serializers?
Any help is appreciated. Thanks!
When your choices are models, the most straightforward approach is to use some derivative of RelatedField. Given that you're using p.id, does PrimaryKeyRelatedField work for you? (Please update your question if it doesn't)
If the default behavior (using model's __unicode__ for the display value) is not what you desire, you can always subclass it and redefine the display_value method:
class CustomPKRelatedField(serializers.PrimaryKeyRelatedField):
"""A PrimaryKeyRelatedField derivative that uses named field for the display value."""
def __init__(self, **kwargs):
self.display_field = kwargs.pop("display_field", "name")
super(CustomPKRelatedField, self).__init__(**kwargs)
def display_value(self, instance):
# Use a specific field rather than model stringification
return getattr(instance, self.display_field)
...
class ObjectTypeSerializer(serializers.ModelSerializer):
...
object_fields = CustomPKRelatedField(queryset=Parameter.objects.all(), many=True)
instance_fields = CustomPKRelatedField(queryset=Parameter.objects.all(), many=True)
...
...
If all you need is so BrowsableAPIRenderer would render a nice-looking <select>, I believe that's all you need to do.
The ChoiceField and MultipleChoiceField are designed to work on a static dataset. They even preprocess things at __init__ to allow grouping. This is why new items don't appear there - those fields essentially "cache" results forever (until the server restart).
If, for some reason, you really need it to be ChoiceField-derivative, you can set up post_save and post_delete singal listeners and update fields' choices (and grouped_choices if you're not on a very bleeding edge version where a PR to allow choices to be set dynamically is already included) attributes. Check the ChoiceField source code for the details. That would be a dirty hack, though. ;)

Django: save() method in form or model?

quite a generic question here but I was curious as to when and in what cases would you use a save method in a ModelForm over the Model class itself and vice versa?
model:
class Model(models.Model):
...
def save(self, *args, **kwargs):
super (Model, self).save(*args, **kwargs)
modelform:
class ModelForm(forms.ModelForm):
...
def save(self, commit=True):
model = super(ModelForm, self).save(commit=True)
if commit:
model.save()
return model
Thanks!
Both are overwriting the default method. Only difference is that Model.save is saving object attributes but ModelForm.save is saving form data. ModelForm is not just a simple Form. It linked to model instance. So it automatically access the fields of corresponding models.
The difference depends on your need/usage. If you know objects will be always be created using ModelForm then you put your logic in ModelForm.save to do stuff which is required before or after saving the data. If you know the objects could be created directly as well which obviously does not go through ModelForm you then write your logic in Model.save to ensure integrirty/consistentency of data. But this does not mean that overriding save both on ModelForm and Model itself is bad, again there will be scenarios where you need both.

ModelForm validation with clean() for field with underscores

I'm trying to implement custom validation on a ModelForm, but I'm having trouble with a field whose name contains an underscore.
Suppose I have this model:
class MyModel(models.Model):
foo = models.IntegerField()
bar_qux = models.IntegerField()
When I try to define custom validation, I use the clean_<fieldname>() methods. For example:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('foo', 'bar_qux')
def clean_foo(self):
# Do stuff
def clean_bar_qux(self):
# Do other stuff
Doing it this way, the clean_foo() method works perfectly, but the clean_bar_qux() one is never invoked, and it seems that the cause is the underscore between bar and qux.
How can I define this method?
An underscore in the field name will not stop the clean method from being called.
Note that the clean_<fieldname> method will not be called if the field validation fails (e.g. if you passed a string 'twenty-two' to the field instead of an integer '22'). If you print or log form.errors after calling form.is_valid(), it might show what the problem is.
I thought I had this issue as well, and I think now I know why. If you read the Django official documentation on form and field validation, you'll find out that each of the fields' to_python and validate are called before reaching clean_some_field. I got it working after subclassing the field and overriding to_python and validate. Hope someone will find this useful at some point!

Django form exclude options in select field

I have one model that has a ManyToMany Field (let's call it "Options") with another Model
When I create the ModelForm it displays all options.
Is there any way to exclude some option values or to show only some of them?
Here is an example:
models.py
class Options (model.Models):
name = ...
...
class Anything (model.Models):
...
options = ManyToManyField(Options)
values of "Options" in my DB:
["OK",
"OK_2",
"NOT_OK",
"OK_3,
"NOT_OK_2"]
Let's say that I need to show ONLY the "OK" values and hide or not to show the "NOT_OK" values.
Is there any way to do this with ModelForms?
You certainly can filter the queryset for a foreign key field or m2m on the related model by using a Form or more commonly a ModelForm.
The reason doing this at form level is useful is because that filtering could well be based on business logic which is not applicable in all cases and so allows more flexibility than defining it against the model for example.
While you can do this while defining the form fields it is best to do it once the form has been constructed and so it takes place at runtime and not compile time (I have just experienced a few interesting occasions where this has caused me some issues, however that was an earlier version of Django!)
The following ModelForm would do the job:
class AnythingForm(ModelForm):
options = forms.MultipleChoiceField()
def __init__(self, **kwargs):
super(AnythingForm, self).__init__(self, **kwargs)
self.fields['options'].queryset = Option.objects.filter({pass in your filters here...})
class Meta:
model = Anything
You can pass the limit_choices_to parameter to your ManyToMany field:
from django.db.models import Q
class Anything (models.Model):
options = models.ManyToManyField(Options,
limit_choices_to=Q(name__startswith='OK'))
In django 1.7 you can even pass a callable in case if list of choices should be changed dynamically.

django ignore changed field in formset

I have the following models:
class Recipe(models.Model):
fields...
class Ingredient(models.Model):
fields...
class UsesIngredient(models.Model):
recipe = models.ForeignKey(Recipe)
ingredient = models.ForeignKey(Ingredient)
amount = models.FloatField()
group = models.CharField()
I have a view which lets the user add any number of 'UsesIngredient' models for a certain recipe through a dynamic formset. The group attribute is automatically filled in an hidden from the user.
The problem is that when the users adds a new form in the formset, but doesn't fill in any of the fields, I don't want that form saved. However, django still tries to save the form because the 'group' attribute has 'changed' (because it has been automatically filled in when the extra form was created).
Is there any way to get around this?
Thanks!
Well, I still didn't feel completely comfortable with Tim Edgar's solution, so I kept looking. I guess I found what I was looking for.
The 'Form' class, has two undocumented methods that are of use in this case: 'has_changed()' and '_get_changed_data'.
During ModelFormSet validation, every form checks 'has_changed()'. If the form did not changed, validation is skipped and a correct form is assumed.
Likewise, during ModelFormSet saving, the save_new_objects checks every form to see if it has changed. If it didn't change, the form isn't saved.
So my solution was to override the has_changed() method to return False if only the 'group' attribute has changed, and all other fields are empty. This is my implementation:
class UsesIngredientForm(forms.ModelForm):
class Meta:
model = UsesIngredient
def has_changed(self, *args, **kwargs):
self._get_changed_data(*args, **kwargs)
# If group is in changed_data, but no other fields are filled in, remove group so
# the form will not be validated or saved
if 'group' in self._changed_data and len(self._changed_data) == 1:
contains_data = False
for name in ['ingredient', 'amount', 'unit']:
field = self.fields[name]
prefixed_name = self.add_prefix(name)
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
if data_value:
contains_data = True
break
if not contains_data:
self._changed_data.remove('group')
return bool(self._changed_data)
Hope this helps anybody in the future!
EDIT:
I edited this answer to reflect Tim Edgars comment.
I realize that this implementation still uses 'private' methods, but I haven't found a cleaner implementation using just the publicly documented methods. But then maybe that is just my own incompetence :).
You could try making all your fields to require a value by setting blank=False. See more here. It should require validation that the values that you care about are not left blank.
If that doesn't work, you can try creating your own custom save method that does the validation that you care about.
def save(self, *args, **kwargs):
# Do your checks on the properties such as self.group, self.amount, etc
# If it is fine then call
super(UsesIngredient, self).save(*args, **kwargs)

Categories

Resources