form.is_valid always false - django

I've been trying to figure out why form.is_valid always return false but I still haven't figured it out yet and it doesn't help that I've just started with Django.
Model
class Post(models.Model):
STATUS_CHOICES = (
('d', 'Draft'),
('p', 'Published'),
)
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=120, null=True, blank=True)
text = models.TextField(max_length=500, null=True, blank=True)
slug = models.SlugField(max_length=40, unique=True)
publish_date = models.DateTimeField(null=True)
status = models.CharField(choices=STATUS_CHOICES, max_length=1,default='d')
Form
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title','author','slug','text','publish_date','status')
widgets = {
'title': forms.TextInput(attrs={'class' : 'form-control', 'placeholder' : 'Title'}),
'author': forms.Select(),
'slug': forms.TextInput(attrs={'class' : 'form-control', 'placeholder' : 'Slug'}),
'text': forms.Textarea(attrs={'class' : 'form-control', 'placeholder' : 'Text'}),
'publish_date': forms.DateInput(attrs={'class' : 'form-control', 'placeholder' : date.today}),
'status': forms.NullBooleanSelect(attrs={'class' : 'form-control'}),
}
View
def admin(request):
if request.user.is_authenticated:
if request.user.is_staff:
users = User.objects.all().order_by('date_joined').reverse()[:5]
posts = Post.objects.all().order_by('created').reverse()[:5]
publications = Publication.objects.all().order_by('created').reverse()[:5]
form = PostForm()
args = {'profile' : users ,'posts' : posts, 'publications' : publications, 'form' : form }
if request.POST:
print('request is posting')
if form.is_valid():
form.save()
else:
print('Error in saving')
return render(request, 'webadmin/index.html',args)
else:
return index(request)
else:
return redirect('/webmaster/login/')
I do hope that you can help me, Thank you OWO)/

Here:
form = PostForm()
if request.POST:
if form.is_valid():
you're trying to validate an unbound form, which is garanteed to fail - you have to pass data (in this case request.POST) to have something to validate. Your code should actually looks something like:
# explicitely test against request.method -
# you can have a POST request with an empty body,
# in which case `request.POST` will be empty and
# have a false value in a boolean test
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
# ...
Now even passing request.POST doesn't mean your form will necessarily validate, but just printing something like "'Error in saving'" will not tell you what's wrong or missing.
An invalid form has an .errors attributes that lists all the validation errors. Those validation errors are automatically displayed in your template if you use {{ form }} (or it's variants {{ form.as_p }}, {{ form.as_table }} etc to render the form in your template (which you did not post here). If you're rendering the form manually, you have to think of rendering the errors by yourself as well as documented here (I do hope you're at least using the form's fields for rendering instead of writing all the html form code manually...).
In all cases you can at least start with printing out form.errors in your view, so you have a clue about what doesn't validate, ie:
if form.is_valid():
form.save()
else:
print("errors : {}".format(form.errors))
Note that all of this is clearly documented, so you could have found the answers by yourself. I understand that Django is a rather large framework and that it can takes time to master it, but it has a rather good tutorial and extensive documentation with lot of examples, so you should really do the tutorial and start browsing the doc, at least to have an idea of what you can find in and where to search when something "doesn't work".

Related

Django - Keep specific fields on form after submit

I have a view that has a simple "save and add another" functionality, that redirects the user to the same page after submit the form.
View:
def new_planning(request):
form = PlanningForm(request.POST)
if form.is_valid():
form.save()
if 'another' in request.POST:
messages.success(request, ('Success!'))
return redirect('new_planning')
else:
return redirect('list_planning')
return render(request, 'pages/planning/new_planning.html', context={
'form': form,
})
Form:
class PlanningForm(forms.ModelForm):
accountplan = ModelChoiceField(
queryset=AccountsPlan.objects.filter(active=True).order_by('code'),
)
month = forms.DateField(
required=True,
error_messages={'required': '', },
)
amount = forms.DecimalField(
max_digits=9,
decimal_places=2,
required=True,
validators=[
error_messages={'required': '', },
)
class Meta:
model = Planning
fields = '__all__'
The function works as expected and after the submit, the same page is rendered with a blank form. What I want is to keep just the "amount" field blank and keep the data typed in the "accountplan" and "month" fields. Is there a way to do this?
I read about instance in the docs, but it doesn't seem to be what I looking for, since I don't want to get the data from the database (if that's possible), but simply keep the last inputs typed in both fields.
If you rewrite the "ModelForm" to a "Model" class, you can get the values of the posted datas, and can be rendered to the page.
For example:
# views.py
def ContactPageView(request):
if request.method == "POST":
email = request.POST.get('email')
message = request.POST.get('message')
message_element = ContactFormObject(email=email, message=message)
message_element.save()
else:
name, message = '', ''
form_data = name, message
return render(request, 'contact.html', {'form_data': form_data})
# models.py
class ContactFormObject(models.Model):
email = models.CharField(max_length=100) #....
ModelForm is more comfortable, but I don't recommend it if you have extra 10 minutes to code some HTML in order to the possibilities of more customization.

POST request not working for Django form and Django formset

I have a form, ApplyJobForm and a Formset, ApplyJobFormset. GET method works when I pass the form and the formset to a view, but for the post request the form and the formset is_valid() isn't working, after clicking submit it returns me to a view without saving. I am unable to save the form with the formset, I don't know what I'm doing wrong here.
Here are my codes.
models.py
class Applicants(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name='applicants')
experience = models.IntegerField(blank=True, null=True)
cv = models.FileField(upload_to=user_directory_path)
degree = models.CharField(choices=DEGREE_TYPE, blank=True, max_length=10)
created_at = models.DateTimeField(default=timezone.now)
def __str__(self):
return f'{self.user.get_full_name()} Applied'
class Certification(models.Model):
applicant = models.ForeignKey(Applicants, on_delete=models.CASCADE, related_name='applicant_certifications')
name = models.CharField(max_length=50)
certification = models.FileField(upload_to=user_directory_path, blank=True)
def __str__(self):
return f'{self.user.get_full_name()} certificate'
forms.py
class ApplyJobForm(forms.ModelForm):
class Meta:
model = Applicants
fields = ('job', 'degree', 'experience', 'cv')
exclude = ('job',)
labels = {
'degree': 'Degree',
'experience': 'Experience',
'cv': 'CV',
}
widgets = {
'degree': forms.Select(attrs={
'class': 'form-control',
}
),
'experience': forms.NumberInput(
attrs={
'class': 'form-control',
}
),
'cv': forms.FileInput(
attrs={
'class': 'form-control',
}
),
}
ApplyFormset = modelformset_factory(
Certification,
fields=('name', 'certification'),
extra=1,
widgets={
'name': forms.TextInput(
attrs={
'class': 'form-control',
'placeholder': 'Certification name'
}
),
'certification': forms.FileInput(
attrs={
'class': 'form-control',
'placeholder': 'Upload certification'
}
)
}
)
views.py
def job_apply(request, job_id=None):
template_name = 'apply_form.html'
applyform = ApplyJobForm(request.GET or None)
job = get_object_or_404(Job, id=job_id)
formset = ApplyFormset(queryset=Certification.objects.none())
if request.method == 'GET':
context = {'applyform': applyform, 'formset': formset}
return render(request, template_name, context)
elif request.method == 'POST':
applyform = ApplyJobForm(request.POST)
formset = ApplyFormset(request.POST)
if applyform.is_valid() and formset.is_valid():
apply = applyform.save(commit=False)
applyform.job = job
apply.save()
for form in formset:
# so that `apply` instance can be attached.
certification = form.save(commit=False)
certification.apply = apply
certification.save()
return redirect('successful-apply')
else:
return redirect('job-detail', id=job.id)
return render(request, template_name, {'applyform': applyform, 'formset': formset})
Here an applicant can add as many certification field when applying for a job, although the certification field is not a required field. Certification model is bound to the Applicants model.
.html
<form class="form" method="POST" action="" enctype="multipart/form-data" role="form" autocomplete="off">
.................
</form>
First of all, never redirect if your forms are not valid. You want to render your template with the invalid form so that you can display the errors to the user. This also helps debugging since you'll see the errors.
So in your view, remove these two lines:
else:
return redirect('job-detail', id=job.id)
so that the invalid case renders the forms in your template.
Next, since you have files to upload, you need to initialise forms that require files with request.FILES:
formset = ApplyFormset(request.POST, request.FILES)
(and the same for applyform).
Finally make sure that in your template you are also displaying all the errors, either on each field ({{ form.<field>.errors }}) or globally ({{ form.errors }}).

How can I autofill author with a model form (video upload)

I need to tie the user to their post but 'author' is not included in the fields of the video upload form so I can't access the field when I save the form.
When I add 'author' to the fields it gives a drop down box. (users shouldn't be able to post as anyone but themselves) I tried just listing the fields individually like so {{form.title}} to keep the author field but not show it to the user, it showed anyway.
In the 'author' field of the VideoPost model I've tried changing out the null=True for these variants on default default=None, default=0, default='None', default=User, default=User.id where User = get_user_model()
When I used default='None' the author dropdown box had the current users name in it, but still allowed a choice, when I tried to post it I got
ValueError: invalid literal for int() with base 10: 'None'
Also, in the views.py, I tried form = VideoPostForm(request.user,request.POST or None, request.FILES or None)
and got CustomUser object has no .get() attribute and that was caused by form.save()
I feel like this might be obvious to someone else but I've been staring at this code for a while now to figure it out.(a couple hours a day doing research and gaining better understanding as to how all of the things I'm doing actually work 'under the hood', I worked on other parts while trying to figure this out because, development speed matters and I could actually figure the other stuff out)
forms.py
class VideoPostForm(forms.ModelForm):
class Meta:
model = VideoPost
fields = ['author','title', 'description', 'file']
views.py
def upload_video(request):
form = VideoPostForm(request.POST or None, request.FILES or None)
if form.is_valid():
form.save(commit=False)
VideoPost.author = request.user
form.save()
return redirect('home')
else:
form = VideoPostForm()
return render(request, 'upload_video.html', {'form': form})
models.py
class VideoPost(models.Model):
objects = models.Manager()
author = models.ForeignKey(User, related_name='video_post', on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=50, null=True, blank=True)
published_date = models.DateTimeField(auto_now_add=True)
description = models.TextField()
validate_file = FileValidator(max_size=52428800)
file = models.FileField(upload_to='videos/', validators=[validate_file])
def __str__(self):
return 'Post by {}'.format(self.author)
template (excluding author field)
<h1>Create Post Page</h1>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<p> <!-- Normally the fields would be form.as_p -->
{{ form.title }}
{{ form.description }}
{{ form.file }}</p>
<button type="submit">Submit New Post</button>
</form>
The views.py is very close. The form.save() method returns an instance of VideoPost. You can then set the author to the current user directly to the new video post object that was created by the form. See code sample below.
views.py
def upload_video(request):
if request.method == "POST":
form = VideoPostForm(request.POST, request.FILES or None)
if form.is_valid():
new_videopost = form.save()
new_videopost.author = request.user
new_videopost.save()
return redirect('home')
else:
form = VideoPostForm()
return render(request, 'upload_video.html', {'form': form})

Django Form Not Saving, Terminal Shows Data & Save Method Added

All I'm trying to do is save the simple form. Everything looks fine but after I click save and the form is re-rendered there is no new trade in the Database. No error message is thrown either.
At first, I thought there was an issue with the user but it looks fine as well. Been reading a lot of documentation on this topic but haven't found where the issue is yet.
Thanks for any help and please let me know if there is anything extra I can add.
create.html
<form id='trade_create_view' method='POST' action='.'>
{% csrf_token %}
{{ form.as_p }}
<input type='submit' value='Submit' >
</form>
views.py
def trade_create_view(request):
form = TradeForm(request.POST or None, instance=request.user)
if form.is_valid():
print(form.cleaned_data)
form.save()
form = TradeForm()
context = {
'form': form,
}
return render(request, "tj/cp/trade/create.html", context)
forms.py
from django import forms
from .models import Trade
class TradeForm(forms.ModelForm):
class Meta:
model = Trade
fields = [
'user',
'target_size',
'target_entry',
'target_exit',
'ticker',
'exchange',
'short',
'comments',
'size',
'entry_price',
'exit_price',
'entry_date',
'exit_date',
'fees',
'permission',
]
exclude = ['user',]
model.py
class Trade(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=False)
comments = models.TextField(max_length=10000, blank=True, null=True)
created = models.DateField(auto_now_add=True)
last_edit = models.DateField(auto_now=True)
#general trade info
ticker = models.ForeignKey(Ticker, on_delete=models.CASCADE)
short = models.BooleanField(default=False)
exchange = models.ForeignKey(Exchange, on_delete=models.CASCADE)
#target trade outline
target_size = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
target_entry = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
target_exit = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
#real trade
size = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
entry_price = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
exit_price = models.DecimalField(null=True, blank=True, max_digits=50, decimal_places=20)
entry_date = models.DateField(blank=True, null=True, default=datetime.now)
exit_date = models.DateField(blank=True, null=True, default=datetime.now)
fees = models.DecimalField(blank=True, null=True, max_digits=50, decimal_places=20)
PER_OPTIONS = [
('0', 'Public'),
('1', 'Private'),
('2', 'Mentor Only'),
]
permission = models.CharField(max_length=1, choices=PER_OPTIONS, default=0)
I think you just have to check if your request is a post or a get method:
def trade_create_view(request):
if request.method == "POST":
form = TradeForm(request.POST or None, instance=request.user)
if form.is_valid():
print(form.cleaned_data)
form.save()
form = TradeForm()
context = {
'form': form,
}
return render(request, "tj/cp/trade/create.html", context)
OR
You just can add a post def to your view as follows:
def post(self, request):
form = TradeForm(request.POST or None, instance=request.user)
if form.is_valid():
print(form.cleaned_data)
form.save()
form = TradeForm()
...
This line doesn't make sens: TradeForm(request.POST or None, instance=request.user)
When you use instance for your form, you give an instance of the object concerned. But your object is Trade not User. You can choose different ways to solve the problem, I give one:
def trade_create_view(request, id):
form = TradeForm()
if request.method == "POST":
trade, created = Trade.objects.get_or_create(id=id) # you get or create your instanced Trade
form = TradeForm(request.POST, instance=trade) # you give to your form the instance of your Trade
if form.is_valid():
print(form.cleaned_data)
form.save()
context = {
'form': form,
}
return render(request, "tj/cp/trade/create.html", context)
I arbitrarily took the id to query an object Trade. As I understand your code you can use this function to create and edite any Trade object with your TradeForm.
get_or_create is document on the Django Website. If you don't when to use it, you can use Trade.objects.get(id=id) you you need to check if the object exist before.
But if you just when to create an object Trade just remove instance=XXX, and use TradeForm(request.POST). Instance is used to get an object in your database and overwrite with the new data posted and processed by your form.
So I switched everything over to Class-Based Views and for some reason now it works perfectly and instantly.
What came even more as a shock is that when I checked my database again after the first Class-Based View test there were about 15 test trades that now appeared (and trust me I checked/refreshed the database after every function view test). So.. I guess it worked all along which is nice because I really read the documentation 10x to figure out what was wrong and was getting really frustrated. What's not so nice is that the database took hours to update or maybe they were frozen somewhere else. That I can't explain yet.
the new views.py
class TradeCreateView(CreateView):
template_name = "tj/cp/trade/create.html"
form_class = TradeForm
queryset = Trade.objects.all()
def form_valid(self, form):
print(form.cleaned_data)
return super().form_valid(form)
Thanks for the help everyone!
I know this maybe not the perfect resolve you guys were hoping for but at least there is some closure.

Cannot validate a django form with images

I'm having a trouble making a form work. As I see it, everything is fine, but is_valid() always returns False (I had to check it in shell mode, since in the template it doesn't show any errors). Am I missing something?
If someone wants to test it, it can be downloaded from http://gitorious.org/e-cidadania
forms.py
from django.forms import ModelForm
from e_cidadania.apps.spaces.models import Space
class SpaceForm(ModelForm):
class Meta:
model = Space
views.py
#permission_required('Space.add_space')
def create_space(request):
space = Space()
if request.POST:
form = SpaceForm(request.POST, request.FILES, instance=space)
if form.is_valid():
handle_uploaded_file(request.FILES['file'])
form.author = request.user
form.date = datetime.datetime.now()
form.save()
return render_to_response('/')
else:
form = SpaceForm()
return render_to_response('spaces/add.html',
{'form': form},
context_instance=RequestContext(request))
models.py
class Space(models.Model):
name = models.CharField(_('Name'), max_length=100, unique=True,
help_text=_('All lowercase. Obligatory.'))
description = models.TextField(_('Description'))
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, verbose_name=_('Author'))
logo = models.ImageField(upload_to='spaces/logos',
verbose_name=_('Logotype'),
help_text=_('100px width, 75px height'))
banner = models.ImageField(upload_to='spaces/banners',
verbose_name=_('Banner'),
help_text=_('75px height'))
authorized_groups = models.ManyToManyField(Group,
verbose_name=_('Authorized groups'))
mod_debate = models.BooleanField(_('Debate module'))
mod_proposals = models.BooleanField(_('Proposals module'))
mod_news = models.BooleanField(_('News module'))
mod_cal = models.BooleanField(_('Calendar module'))
mod_docs = models.BooleanField(_('Documents module'))
form.errors shows no errors?
When files are involved, check if request.FILES actually has a file.
Ensure your <form> has <form enctype="multipart/form-data" ...> .. this is the culprit in many cases.
All the google results for that error revolve around PIL. Especially if you're on a mac!
http://salamand.wordpress.com/2009/08/25/problem-uploading-image-file-to-satchmo/
http://djangodays.com/2008/09/03/django-imagefield-validation-error-caused-by-incorrect-pil-installation-on-mac/
http://mail.python.org/pipermail/image-sig/2002-August/001947.html
The problem was that the model fields author and date were not declared as blank=True, null=True. Because of that the form never validated, because even if you don't commit the save(), the save command does validate the form.
Yuji probably answered your question but I would like to give a tip on how to make view cleaner (same meaning, a bit less code and readability same or even better):
#permission_required('Space.add_space')
def create_space(request):
space = Space()
form = SpaceForm(request.POST or None, request.FILES or None, instance=space)
if request.POST and form.is_valid():
handle_uploaded_file(request.FILES['file'])
form.author = request.user
form.date = datetime.datetime.now()
form.save()
return render_to_response('/')
return render_to_response('spaces/add.html',
{'form': form},
context_instance=RequestContext(request))
I don't know if it will help, but once i got some errors when I checked permissions for user, that submit the form, and this makes the trick:
if form.is_valid():
new_space = form.save(commit = False)
new_space.author = request.user
...
new_space.save()