Model class
class Fuzz_Engine(models.Model):
id = PositiveTinyIntField(primary_key = True)
engine_name = models.CharField(max_length=16)
version = models.CharField(max_length = 16)
class Meta:
db_table = 'fuzz_engine'
unique_together = ('engine_name', 'version')
class AddFuzzEngineForm(ModelForm):
class Meta:
model = Fuzz_Engine
View Class
def addengine(request)
if request.method == 'POST':
form = AddFuzzEngineForm(request.POST)
# input validation for add phone model form
if form.is_valid():
fuzzEngineToAdd = Fuzz_Engine (engine_name = request.POST['engine_name'], version = request.POST['version'])
fuzzEngineToAdd.save(force_insert=True)
return render_to_response('fuzz/fuzz_cengine_results.html', {'fid': fuzzEngineToAdd.id,'fe': fuzzEngineToAdd,},context_instance=RequestContext(request))
else:
form = AddFuzzEngineForm()
return render_to_response('fuzz/add_fuzz_engine.html', {'form': form},context_instance=RequestContext(request))
I have looked into a few similar questions on this issue, tried to print out the errors but doesn't seem to appear.
Django Formsets - form.is_valid() is False preventing formset validation
form.is_valid() always returning false
I have a feeling that the cause of the error lies in the structure of my model form class.
The .is_valid is false as I have placed a code in that if statement and it doesn't run, however if I have an else statement(which is not here) for if it is not valid, it will appear.
Can anyone provide another way of debugging this kind of error?
Couple of issues, it's hard to debug the code if the code you paste in isn't formatted well. The indentations were a mess so I'm not sure if that's causing a problem.
It seems like you are manually assigning a foreign key for your model. I would suggest just letting django handle the id for the model:
class Fuzz_Engine(models.Model):
engine_name = models.CharField(max_length=16)
version = models.CharField(max_length = 16)
class Meta:
db_table = 'fuzz_engine'
unique_together = ('engine_name', 'version')
Your form looks fine:
class AddFuzzEngineForm(ModelForm):
class Meta:
model = Fuzz_Engine
Some problems I see in your views include:
you shouldn't use request.POST['field_names'] directly. you should be getting the cleaned_data from your form.
you can save the form directly because it is a ModelForm. if you need the instance that you just created, that is what is returned from the save method, you can set a variable and use that as shown below.
def addengine(request)
if request.method == 'POST':
form = AddFuzzEngineForm(request.POST)
if form.is_valid():
instance = form.save()
return render_to_response('fuzz/fuzz_cengine_results.html', {'fid': instance.id,'fe': instance,},context_instance=RequestContext(request))
else:
form = AddFuzzEngineForm()
return render_to_response('fuzz/add_fuzz_engine.html', {'form': form},context_instance=RequestContext(request))
With your original view, it looks like you are trying to save a Fuzz_Engine instance with no id.
DTing has some great points, but I suspect your actual problem is related to your explicit definition of the id field on your model. Unless you have a really good reason, you should never do this - Django defines an autoincrement field automatically, and there is rarely any point overriding this, unless you are using a legacy db that can't be changed.
In your case, you have defined it as a tinyint without autoincrement. That means that field is going to be required on any form, as it needs to be specified manually every time you create a new instance. You haven't shown the template you're using to display the form so it's impossible to be sure, but I imagine you're not showing this field at all.
If you really really want to carry on doing it this way, you will need to specify exclude = ('id',) on the form Meta. Then in your is_valid clause, taking on board DTing's recommendations, you'll need to do this:
if form.is_valid():
instance = form.save(commit=False)
instance.id = some_function_for_calculating_id()
instance.save()
But as I say, you shouldn't be doing that at all.
Related
I spent a lot of hours searching for a feature which I think should be quite a basic functionality in Django. But I just can't get it working,
I am unable to find a widget which will function same as m2m widget of django, but will also create new model instance if it doesn't exists.
Note: Here model instance already exists means that data entered in inline widget already exists in database.
E.g.
If I had models as:
class Outcome(models.Model):
outcome = models.CharField(max_length=255)
outcome_short_name = models.CharField(max_length=10, blank=True, null=True)
class Course(models.Model):
course_title = models.CharField(
verbose_name=COURSE_SINGULAR + " title", max_length=200, unique=True
)
course_outcome = models.ManyToManyField(
Outcome, verbose_name=COURSE_SINGULAR + " outcome", blank=True
)
Then I want "Outcomes" shown as this image while creating course:
Image of adding new course with inline outcomes
Now, If the outcomes data added by user already exists, then it should only map them to course. Otherwise it should first store outcomes into database and then map them to course.
Any guidance in right direction will be highly appreciated.
Thanks,
EDIT:
As suggested by #dirkgroten to use modelformset, I changed my FormView as:
class CourseFormView(FormView):
template_name = "course/course_form.html"
form_class = CourseForm
success_url = "/admin/"
def get_context_data(self, **kwargs):
context = super(CourseFormView, self).get_context_data(**kwargs)
if self.request.POST:
context["outcomes"] = OutcomeFormSet(self.request.POST)
else:
context["outcomes"] = OutcomeFormSet(queryset=Outcome.objects.none())
return context
def form_valid(self, form, **kwargs):
super(CourseFormView, self).get_context_data(**kwargs)
context = self.get_context_data()
outcomes_formset = context["outcomes"]
if not outcomes_formset.is_valid():
return super().form_invalid(form)
cleaned_data = form.cleaned_data
cleaned_data.pop("course_outcome")
course = Course.objects.create(**cleaned_data)
course.save()
outcomes_formset.instance = course
outcomes_formset.save()
course.course_outcome.set(Outcome.objects.filter(course_outcome=course))
return super().form_valid(form)
Everything looks fine except my model_formset is not validated if form data in formset already exists in database.
E.g. if I enter (outcome="test_outcome", outcome_short_name="test_short") in formset and same data already exists in outcome table, then my formset gives error:
Outcome with this Outcome and Outcome short name already exists.
Is there any way to tackle this situation or I am doing something wrong.
You can test above at: http://code.gdy.club:8001/course/add/
outcomes_list: http://code.gdy.club:8001/outcome/
Thanks,
--
Suraj
https://hacksj4u.wordpress.com
https://github.com/SurajDadral
You need to handle the case where an Outcome already exists yourself. The default when validating the form is to assume a new object will be created, so if your fields are set to be unique_together, then the individual form will not validate.
You could do it on the OutcomeFormset's clean() method like this:
from django.core.exceptions import NON_FIELD_ERRORS
def clean(self):
super().clean()
for i in range(0, self.total_form_count()):
form = self.forms[i]
if form.non_field_errors() and len(form.errors) == 1:
# the only error is probably due to unique_together constraint
try:
form.instance = Outcome.objects.get(
outcome=form.data.get(form.add_prefix('outcome')),
outcome_short_name=form.data.get(form.add_prefix('outcome_short_name')))
except Outcome.DoesNotExist:
pass # some other error so we should keep it
else:
del form._errors[NON_FIELD_ERRORS]
Then in your view, when saving the formset, you should loop through all forms and not save the ones where the instance has a pk:
for form in outcomes_formset:
outcome = form.instance
if not outcome.pk:
outcome = form.save()
course.course_outcome.add(outcome)
I think I have a rather uncommon feature here, at least I couldn't find a answer.
What I'd like to achieve is a text input with autocomplete on a related model with its label field. the given text should then get_or_create the related model. this already works but the problem is, that the new related model instance is saved on form submit no matter if the form is_valid or not.
given the following situation and implementation (shortened for better overview)
class Correspondent(models.Model):
label = models.CharField(_("label"), max_length=100, unique=True)
class Document(BaseModel):
correspondent = models.ForeignKey(
Correspondent,
verbose_name=_("correspondent"),
on_delete=models.PROTECT,
related_name="documents",
)
with the following form:
class DocumentForm(forms.ModelForm):
correspondent = forms.CharField(
label=_("correspondent"), widget=forms.TextInput(attrs={"class": "awesomplete"})
)
class Meta:
model = Document
exclude = ["created_by", "modified_by"]
def clean_correspondent(self, *args, **kwargs):
# ToDo: don't save the FKed instance if complete form isn't valid
user = self.initial["user"]
data = self.cleaned_data["correspondent"]
obj = Correspondent.objects.get_or_create(label=data)
return obj
so the problem here is obj.save() which is called before form.is_valid() and I couldn't figure a way yet how to solve this. I'd like to prevent the creation of a new Correspondent instance if the form isn't valid yet.
Let me know if I can improve this question and thx for any hints.
Thx to Dipen Dadhaniya for pushing me in the right direction.
I needed to exclude the actual field from the ModelForm so I could just clean in a more simple way as CharField needs it. Afterwards in the View I can do the logic, after the whole form is valid indeed.
the form looses the complete custom clean_correspondent method.
class DocumentForm(forms.ModelForm):
correspondent_label = forms.CharField(
label=_("correspondent"), widget=forms.TextInput(attrs={"class": "awesomplete"})
)
class Meta:
model = Document
exclude = ["correspondent", "created_by", "modified_by"]
the logic is now in the DocumentCreateView in the form_valid() method.
def form_valid(self, form):
form.instance.modified_by = self.request.user
form.instance.correspondent, created = Correspondent.objects.get_or_create(
label=form.cleaned_data["correspondent_label"]
)
return super().form_valid(form)
Bingo! :)
I have come to an impasse when using a ModelForm.
I'm extending the User model that comes with Django, and I'm also using a ModelForm so the user can edit it.
Following the same example in the documentation, I would have this code.
models.py
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# In this case, department is optional, so I have set 'blank' and 'null' to True.
department = models.CharField(max_length=100, blank=True, null=True)
forms.py
class DepartmentForm(ModelForm):
class Meta:
model = Employee
fields = ['department',]
The problem comes at the view. I found that I need to pass an instance of the model to the form so the save() function works without having to customize it, but of course, user.employee has not been created yet, therefore it throws an error.
views.py
def DepartmentView(request):
# Here is the issue.
department = request.user.employee
if request.method == 'POST':
# I need to pass the instance here.
form = DepartmentForm(request.POST, instance=department)
if form.is_valid():
form.save()
else:
# And also here so it autocompletes the form.
form = DepartmentForm(instance=department)
return render(request, 'employee.html', {'form': form})
It works if I manually add a value to user.employee.department through the shell and then reload the page, otherwise the error is as follow.
RelatedObjectDoesNotExist at [something]
User has no employee.
Or something like that... I'm sorry, I didn't try the code above so the error could be a little different, but the concept is exactly the same.
I'm also sorry if this has been asked before. I did a Google search and couldn't find an answer to this issue.
You could use get_or_create to fetch the employee from the db, or create it if it doesn't exist.
department, created = Employee.objects.get_or_create(user=request_or_user, department='')
if request.method == 'POST':
form = DepartmentForm(request.POST, instance=department)
...
Another option is to use a signal, so that the related model is created when the user is created. Then you can assume that the employee already exists, and you can use request.user.employee instead of get_or_create.
My form:
class PlanForm(forms.ModelForm):
owner = forms.ModelChoiceField(label="",
queryset=Profile.objects.all(),
widget=forms.HiddenInput())
etc...
class Meta:
model = Plan
Owner, in the model, is a ForeignKey to a Profile.
When I set this form, I set the value of "owner" to be a Profile object.
But when this comes out on the form, it seems to contain the name of the Profile like this:
<input type="hidden" name="owner" value="phil" id="id_owner" />
When the form is submitted and gets back to my views.py I try to handle it like this:
form = PlanForm(request.POST)
...
if form.is_valid():
plan = form.save()
return HttpResponseRedirect('/plans/%s'%plan.id) # Redirect after POST
However, what I get is a type-conversion error as it fails to turn the string "phil" (the user's name that was saved into the "owner" field) into an Int to turn it into the ForeignKey.
So what is going on here. Should a ModelForm represent a foreign key as a number and transparently handle it? Or do I need to extract the id myself into the owner field of the form? And if so, how and when do I map it back BEFORE I try to validate the form?
I suspect that the __unicode__ method for the Profile model instance, or the repr thereof is set to return a value other than self.id. For example, I just set this up:
# models.py
class Profile(models.Model):
name = models.CharField('profile name', max_length=10)
def __unicode__(self):
return u'%d' % self.id
class Plan(models.Model):
name = models.CharField('plan name', max_length=10)
profile = models.ForeignKey(Profile, related_name='profiles')
def __unicode__(self):
return self.name
# forms.py
class PlanForm(forms.ModelForm):
profile = forms.ModelChoiceField(queryset=Profile.objects.all(),
widget=forms.HiddenInput())
class Meta:
model = Plan
# views.py
def add_plan(request):
if request.method == 'POST':
return HttpResponse(request.POST['profile'])
profile = Profile.objects.all()[0]
form = PlanForm(initial={'profile':profile})
return render_to_response('add_plan.html',
{
'form':form,
},
context_instance=RequestContext(request))
With that, I see PlanForm.profile rendered thus in the template:
<input type="hidden" name="profile" value="1" id="id_profile" />
Hmm...
This might actually be a security hole.
Suppose a malicious attacker crafted a POST (say, by using XmlHttpRequest from FireBug) and set the profile term to some wacky value, like, your profile ID. Probably not what you wanted?
If possible, you may want to get the profile from the request object itself, rather than what's being submitted from the POST values.
form = PlanForm(request.POST)
if form.is_valid():
plan = form.save(commit=False)
plan.owner = request.user.get_profile()
plan.save()
form.save_m2m() # if neccesary
When you assign a Profile object to the form, Django stringifies it and uses the output as the value in the form. What you would expect though, is for Django to use the ID of the object instead.
Luckily, the workaround is simple: Just give the form primary key values of the Profile objects instead:
form = PlanForm(initial={'profile': profile.pk})
On the other end, when you're working with bound forms, however, they work much more sensibly:
form = PlanForm(request.POST)
if form.is_valid():
print form.cleaned_data['profile'] # the appropriate Profile object
There's usually no need to put related object into form field. There's a better way and this is specifying parent id in form URL.
Let's assume you need to render a form for new Plan object and then create one when form is bubmitted. Here's how your urlconf would look like:
(r"/profile/(?P<profile_id>\d+)/plan/new", view.new_plan), # uses profile_id to define proper form action
(r"/profile/(?P<profile_id>\d+)/plan/create", view.create_plan) # uses profile_id as a Plan field
And if you're changing existing object, all you need is plan_id, you can deduce any related record from it.
Since ModelChoiceField inherits from ChoiceFIeld, you should use the MultipleHiddenInput widget for this:
class PlanForm(forms.ModelForm):
owner = forms.ModelChoiceField(
queryset=Profile.objects.all(),
widget=forms.MultipleHiddenInput())
class Meta:
model = Plan
I have a simple model that is defined as:
class Article(models.Model):
slug = models.SlugField(max_length=50, unique=True)
title = models.CharField(max_length=100, unique=False)
and the form:
class ArticleForm(ModelForm):
class Meta:
model = Article
The validation here fails when I try to update an existing row:
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid(): # POOF
form.save()
Creating a new entry is fine, however, when I try to update any of these fields, the validation no longer passes.
The "errors" property had nothing, but I dropped into the debugger and deep within the Django guts I saw this:
slug: "Article with this None already exists"
So it looks like is_valid() fails on a unique value check, but all I want to do is update the row.
I can't just do:
form.save(force_update=True)
... because the form will fail on validation.
This looks like something very simple, but I just can't figure it out.
I am running Django 1.0.2
What croaks is BaseModelForm.validate_unique() which is called on form initialization.
I don't think you are actually updating an existing article, but instead creating a new one, presumably with more or less the same content, especially the slug, and thus you will get an error. It is a bit strange that you don't get better error reporting, but also I do not know what the rest of your view looks like.
What if you where to try something along these lines (I have included a bit more of a possible view function, change it to fit your needs); I haven't actually tested my code, so I am sure I've made at least one mistake, but you should at least get the general idea:
def article_update(request, id):
article = get_objects_or_404(Article, pk=id)
if request.method == 'POST':
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
form.save()
return HttpResponseRedirect(to-some-suitable-url)
else:
form = ArticleForm(instance=article)
return render_to_response('article_update.html', { 'form': form })
The thing is, as taurean noted, you should instantiate your model form with the object you wish to update, otherwise you will get a new one.
I was also searching for a way to update an existing record, even tried form.save(force_update=True) but received errors??
Finally by trial & error managed to update existing record. Below codes tested working. Hope this helps...
models.py from djangobook
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField(blank=True, verbose_name='e-mail')
objects = models.Manager()
sel_objects=AuthorManager()
def __unicode__(self):
return self.first_name+' '+ self.last_name
class AuthorForm(ModelForm):
class Meta:
model = Author
# views.py
# add new record
def authorcontact(request):
if request.method == 'POST':
form = AuthorForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contact/created')
else:
form = AuthorForm()
return render_to_response('author_form.html', {'form': form})
update existing record
def authorcontactupd(request,id):
if request.method == 'POST':
a=Author.objects.get(pk=int(id))
form = AuthorForm(request.POST, instance=a)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contact/created')
else:
a=Author.objects.get(pk=int(id))
form = AuthorForm(instance=a)
return render_to_response('author_form.html', {'form': form})
All i can guess is that you are getting an object to fill a form, and trying to save it again.
Try using a ModelForm, and intantiate it with desired object.
It appears that your SlugField is returning None and because a null/blank slug already exists somewhere in the database, its giving an 'already exists' error. It seems like your slug field isn't saving correctly at all.