Django ModelForm not calling clean - python

I am performing a basic Django ModelForm create/validate/save operation. My custom clean methods are not being called when is_valid() is being called when running the code under the Eclipse debugger and I set a breakpoint after the form creation and the call to is_valid().
I have traced through the Django base code numerous times and it appears that the error dictionary on the ModelForm class is never set to None, which triggers the validation. I suspect that this is due to an interaction with the debugger accessing the _errors attribute of the ModelForm to display in the variables pane.
When I remove all breakpoints and let the code flow naturally, I can prove that the custom clean code is running by issuing print statements.
Is this a flaw in the Django ModelForm design, an Eclipse problem or am I barking up the wrong tree?
models.py:
from django.db import models
class TestModel1(models.Model):
field1 = models.CharField(max_length=45)
field2 = models.IntegerField(default=2)
field3 = models.CharField(max_length=45, null=True, blank=True)
forms.py:
from order.models import TestModel1
from django.forms import ModelForm
class OrderTestForm(ModelForm):
def clean_field1(self):
return self.cleaned_data['field1']
def clean_field2(self):
return self.cleaned_data['field2']
class Meta:
model = TestModel1
My test harness:
from forms import OrderTestForm
row = {'field1': 'test value', 'field2': '4', }
ff = OrderTestForm(row)
#ff.full_clean()
if ff.is_valid():
ff.save()
else:
print ff.errors

I have found the "answer" which is sort of a non-answer. The exact use case was receiving a CSV file from a customer. After inspection of the customer's actual data file, the data fields were padded with spaces -- many spaces. I trimmed the input and shoved that trimmed dictionary into the form and everything worked. Still does not explain why eclipse choked on this.

I had the same problem and tried to dig a little deeper and debug the framework.
There's probably a difference between regular forms and model forms which causes this not to work, but this hack (overriding is_valid() instead of clean(...)) worked for me:
def is_valid(self):
#run whatever ModelForm validations you need
return super(OrderTestForm, self).is_valid()

What if you try:
from order.models import TestModel1
from django.forms import ModelForm
class OrderTestForm(ModelForm):
class Meta:
model = TestModel1
def clean_field1(self):
value = self.cleaned_data['field1']
print value
return value
def clean_field2(self):
value = self.cleaned_data['field2']
print value
return value

Related

Pycharm debugger, does not go deep enough : Django project. [ fairly new to python/Django ]

I'm currently debugging my Django project [kind of first project in Django/python]. The debug 'Step Over' button in Pycharm seems to be going properly. However at a particular point, it does not go deep enough.
Below is the code with the debug point (commented that particular line).
Please note, it's a properly working project, although I have trimmed down the codes (like skipping import statements, class definitions etc) here to make it as simple as possible to represent my question.
File: create_report_view.py Here is where I have set the debug point.
from project.project_core.forms import BrandModelForm
def post(self, request):
brand_form = BrandModelForm(request.POST, request.FILES)
if brand_form.is_valid(): # Debug point
file_name_errors, csv_name_errors, file_name_alphanumeric_error = self._validate_data(brand_form)
print(" And debug step goes on... ")
So, the debugger skips what happens inside the .is_valid() call, where my validation runs, and jumps to next line in the above code. How do I force it to debug the .is_valid() method as well ?
Dependent code blocks.
Below is the definition of my model, where i mention the validators in it.
File : models.py
from django.db.models import Model
from project.project_core.validators import ReportTypeValidator
class BrandModel(Model):
report_model = models.ForeignKey(ReportModel, models.CASCADE)
name = models.CharField(max_length=256, db_index=True)
review_file = models.FileField(upload_to='reviews/', validators=[ReportReviewValidator])
number_of_reviews = models.IntegerField(blank=True, null=True)
File : forms.py
from project.project_core.models import BrandModel
class BrandModelForm(forms.ModelForm):
class Meta:
model = BrandModel
exclude = ('report_model',)
labels = {
'name': 'Brand Name'
}
File :validators.py
from django.utils.deconstruct import deconstructible
ReportReviewValidator = UploadFileValidator(REVIEW_FILE_COLUMNS, CHARACTER_LIMIT_CONFIGS)
#deconstructible
class UploadFileValidator:
def __init__(self, review_file_columns: List[str], character_limit_configs: List[CharacterLimitConfig]):
self.csv_column_validator = CsvColumnsValidator(self.review_file_columns)
self.char_limit_validator = CharLimitValidator(self.char_limit_configs)
def __call__(self, field_file: FieldFile) -> None:
row_list = self.csv_column_validator(field_file)
I want the debugger to reach the above block of code, i.e the class UploadFileValidator
What should I be doing for that ?
To achieve this, you need to put your breakpoint one line above and use "step into" to get into it:
def post(self, request):
brand_form = BrandModelForm(request.POST, request.FILES) # Debug point
if brand_form.is_valid(): # Step Into this
file_name_errors, csv_name_errors, file_name_alphanumeric_error = self._validate_data(brand_form)
Note that this will get you to the is_valid() method, which is django's way of checking if your form is valid. It is not going to instantly take you to your validator. You will have to navigate through the whole process of django's form validation, and at some point you will end up in your own validator. If you want to specifically get into your validator's code, put a breakpoint inside instead.

Django form not calling clean_<fieldname> (in this case clean_email)

I couldn't find an answer to the following question, it took me a couple of hours to find out, hence I'm adding it. I'll add my approach of solving it and the answer.
I'm following a YouTube tutorial from This person. For some reason I'm typing the same code, and I checked every single letter. Yet for some reason my cleaning functions aren't called. It's probably something simple, especially since a related question showed something similar. It's probably a framework thing that I get wrong, but I wouldn't know what it is.
Here is the relevant code.
forms.py (complete copy/paste from his Github)
from django import forms
from .models import SignUp
class ContactForm(forms.Form):
full_name = forms.CharField(required=False)
email = forms.EmailField()
message = forms.CharField()
class SignUpForm(forms.ModelForm):
class Meta:
model = SignUp
fields = ['full_name', 'email']
### exclude = ['full_name']
def clean_email(self):
email = self.cleaned_data.get('email')
email_base, provider = email.split("#")
domain, extension = provider.split('.')
# if not domain == 'USC':
# raise forms.ValidationError("Please make sure you use your USC email.")
if not extension == "edu":
raise forms.ValidationError("Please use a valid .EDU email address")
return email
# Final part is ommited, since it's not relevant.
admin.py (typed over from the tutorial)
from django.contrib import admin
# Register your models here.
from .models import SignUp
from .forms import SignUpForm
class SignUpAdmin(admin.ModelAdmin):
list_display = ['__unicode__', 'timestamp', 'updated']
class Meta:
model = SignUp
form = SignUpForm
admin.site.register(SignUp, SignUpAdmin)
After using print statements for a while and reading questions that seemed similar but eventually didn't solve my problem, I decided to look into the source of Django (idea inspired by the most similar question I could find).
Then, I decided to debug the source, since I wanted to know how Django is treating my customized function (thanks to a tutorial + SO answer). In the source I found that the customized functions were called around return super(EmailField, self).clean(value) (line 585, django/forms/fields.py, Django 1.8). When I was stepping through the code I found the critical line if hasattr(self, 'clean_%s' % name): (line 409, django/forms/forms.py, Django 1.8). I checked for the value name which was "email". Yet, the if-statement evaluated as False ((Pdb) p hasattr(self, 'clean_%s' % name)). I didn't understand why, until I figured out that the function name was not registered ((Pdb) pp dir(self)).
I decided to take a look at the whole source code repository and cross-checked every file and then I found that
class Meta:
model = SignUp
form = SignUpForm
means that form / SignUpForm were nested inside the Meta class. At first, I didn't think much of it but slowly I started to realize that it should be outside the Meta class while staying main class (SignUpAdmin).
So form = SignUpForm should have been idented one tab back. For me, as a Django beginner, it still kind of baffles me, because I thought the Meta class was supposed to encapsulate both types of data (models and forms). Apparently it shouldn't, that's what I got wrong.

Django Admin - how to prevent deletion of some of the inlines

I have 2 models - for example, Book and Page.
Page has a foreign key to Book.
Each page can be marked as "was_read" (boolean), and I want to prevent deleting pages that were read (in the admin).
In the admin - Page is an inline within Book (I don't want Page to be a standalone model in the admin).
My problem - how can I achieve the behavior that a page that was read won't be deleted?
I'm using Django 1.4 and I tried several options:
Override "delete" to throw a ValidationError - the problem is that the admin doesn't "catch" the ValidationError on delete and you get an error page, so this is not a good option.
Override in the PageAdminInline the method - has_delete_permission - the problem here -it's per type so either I allow to delete all pages or I don't.
Are there any other good options without overriding the html code?
Thanks,
Li
The solution is as follows (no HTML code is required):
In admin file, define the following:
from django.forms.models import BaseInlineFormSet
class PageFormSet(BaseInlineFormSet):
def clean(self):
super(PageFormSet, self).clean()
for form in self.forms:
if not hasattr(form, 'cleaned_data'):
continue
data = form.cleaned_data
curr_instance = form.instance
was_read = curr_instance.was_read
if (data.get('DELETE') and was_read):
raise ValidationError('Error')
class PageInline(admin.TabularInline):
model = Page
formset = PageFormSet
You could disable the delete checkbox UI-wise by creating your own custom
formset for the inline model, and set can_delete to False there. For
example:
from django.forms import models
from django.contrib import admin
class MyInline(models.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(MyInline, self).__init__(*args, **kwargs)
self.can_delete = False
class InlineOptions(admin.StackedInline):
model = InlineModel
formset = MyInline
class MainOptions(admin.ModelAdmin):
model = MainModel
inlines = [InlineOptions]
Another technique is to disable the DELETE checkbox.
This solution has the benefit of giving visual feedback to the user because she will see a grayed-out checkbox.
from django.forms.models import BaseInlineFormSet
class MyInlineFormSet(BaseInlineFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
if some_criteria_to_prevent_deletion:
form.fields['DELETE'].disabled = True
This code leverages the Field.disabled property added in Django 1.9. As the documentation says, "even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data," so you don't need to add more code to prevent deletion.
In your inline, you can add the flag can_delete=False
EG:
class MyInline(admin.TabularInline):
model = models.mymodel
can_delete = False
I found a very easy solution to quietly avoid unwanted deletion of some inlines. You can just override delete_forms property method.
This works not just on admin, but on regular inlines too.
from django.forms.models import BaseInlineFormSet
class MyInlineFormSet(BaseInlineFormSet):
#property
def deleted_forms(self):
deleted_forms = super(MyInlineFormSet, self).deleted_forms
for i, form in enumerate(deleted_forms):
# Use form.instance to access object instance if needed
if some_criteria_to_prevent_deletion:
deleted_forms.pop(i)
return deleted_forms

Django admin error in many-to-many relationship

For example.
class One(models.Model):
text=models.CharField(max_length=100)
class Two(models.Model):
test = models.Integer()
many = models.ManyToManyField(One, blank=True)
When I try save my object in admin panel, I take error such as:
"'Two' instance needs to have a primary key value before a many-to-many relationship can be used."
I use django 1.3. I tried add AutoField to Two class, but it's not work too.
This is my code.
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.core.urlresolvers import reverse
from project.foo.forms import FooForm
from project.foo.models import Foo
from project.fooTwo.views import fooTwoView
def foo(request, template_name="foo_form.html"):
if request.method == 'POST':
form = FooForm(data=request.POST)
if form.is_valid():
foo = Foo()
foo.name = request.POST.get("name")
foo.count_people = request.POST.get("count_people")
foo.date_time = request.POST.get("date_time")
foo.save()
return fooTwoView(request)
else:
form = FooForm()
return render_to_response(template_name, RequestContext(request, {
"form": form,
}))
P.S. I find my fail. It is in model. I used many-to-many in save method. I add checking before using, but it's not help.
class Foo(models.Model):
name = models.CharField(max_length=100, null=False, blank=False)
count_people = models.PositiveSmallIntegerField()
menu = models.ManyToManyField(Product, blank=True, null=True)
count_people = models.Integer()
full_cost = models.IntegerField(blank=True)
def save(self, *args, **kwargs):
if(hasattr(self,'menu')):
self.full_cost = self.calculate_full_cost()
super(Foo, self).save(*args, **kwargs)
def calculate_full_cost(self):
cost_from_products = sum([product.price for product in self.menu.all()])
percent = cost_from_products * 0.1
return cost_from_products + percent
I try hack in save method such as
if(hasattr(self,Two)):
self.full_cost = self.calculate_full_cost()
This is help me, but i dont think that is the django way. What is interesting, that is without this checking admin panel show error, but create object. Now, if i select item from Two and save, my object does not have full_cost, but when i view my object, admin panel remember my choice and show me my Two item, what i select... I dont know why.
How do i save this?
There are quite a few problems with your code. The most obvious one are
1/ in your view, using a form for user inputs validation/sanitization/conversion then ignoring the santized/converted data and getting unsanitized inputs directly from the request. Use form.cleaned_data instead of request.POST to get your data, or even better use a ModelForm which will take care of creating a fully populated Foo instance for you.
2/ there's NO implicit "this" (or "self" or whatever) pointer in Python methods, you have to explicitely use "self" to get at the instance attributes. Here's what your model's "save" method really do:
def save(self, *args, **kwargs):
# test the truth value of the builtin "id" function
if(id):
# create a local variable "full_cost"
full_cost = self.calculate_full_cost()
# call on super with a wrong base class
super(Banquet, self).save(*args, **kwargs)
# and exit, discarding the value of "full_cost"
Now with regard to your question: Foo.save is obviously not the right place to compute someting based on m2m related objects. Either write a distinct method that run the computation AND update Foo AND save it and call it after the m2m are saved (hint : a ModelForm will take care of saveing the m2m related objects for you), or just use the m2m_changed signal.
This being said, I strongly suggest you spend a few hours learning Python and Django - it will save you a lot of time.
Why not use "OneToOneField" instead of Many-to-Many

How can I change inline forms in django admin?

In my django projects I have 2 related models "Vehicle"(parent model) and ParamConf(child model). ParamConf has field "program" and I want to fix some wrong values in it.
(admin.py):
class ParamConfFormSet(BaseInlineFormSet):
def clean(self):
super(ParamConfFormSet, self).clean()
for form in self.forms:
if hasattr(form, 'cleaned_data') and 'program' in form.cleaned_data:
program = form.cleaned_data['program'].lower() # <<< I want to save this changed value
form.cleaned_data['program'] = program # <<< but this doesn't work :^(
class ParamConfInline(admin.TabularInline):
model = models.ParamConf
formset = ParamConfFormSet
class VehicleAdminForm(forms.ModelForm):
class Meta:
model = models.Vehicle
class VehicleAdmin(admin.ModelAdmin):
inlines = [ ParamConfInline, ]
form = VehicleAdminForm
I even wrote save() method for ParamConf, but django doesn't want to call it after saving a Vehicle form(Vehicle's save method is alright).
Django emits save signals for inline parameters if field values actually changed (and I was trying to save form without editing fields). Sorry it's my fault.
I know that the question is very old, but someone else may be still looking for the answer...Saving a inline even if it was not changed:
Class EntityInline(admin.TabularInline):
extra = 0
model = Entity
form = AlwaysChangedModelForm

Categories

Resources