django modelformset_factory doesn't include actual forms - python

I've been trying to follow tutorials and other SO questions and have a modelformset_factory that's displaying a list of what looks like forms in the html, but it turns out they're not actual forms.
html that gets displayed:
<div ='container'>
<div class='row'><tr><th><label for="id_form-0-config_key">Config key:</label></th><td><input id="id_form-0-config_key" maxlength="63" name="form-0-config_key" type="text" value="ClientMustVerify" /></td></tr>
<tr><th><label for="id_form-0-config_value">Config value:</label></th><td><input id="id_form-0-config_value" maxlength="63" name="form-0-config_value" type="text" value="TRUE" /><input id="id_form-0-id" name="form-0-id" type="hidden" value="3" /></td></tr> <input type="submit" value="Update" /></div>
<div class='row'><tr><th><label for="id_form-1-config_key">Config key:</label></th><td><input id="id_form-1-config_key" maxlength="63" name="form-1-config_key" type="text" value="Auditing" /></td></tr>
<tr><th><label for="id_form-1-config_value">Config value:</label></th><td><input id="id_form-1-config_value" maxlength="63" name="form-1-config_value" type="text" value="FALSE" /><input id="id_form-1-id" name="form-1-id" type="hidden" value="4" /></td></tr> <input type="submit" value="Update" /></div>
<div>
notice there is no form tag anywhere. working backwards, here's the excerpt from the template:
<div ='container'>
{% for form in formset %}
<div class='row'>{{form}} <input type="submit" value="Update" /></div>
{% endfor %}
<div>
yes, I added the submit button manually hoping to get these to work, but of course if there isn't a form tag, then the submit button won't do anything.
views.py:
from limbo.models import serverConfig
from django.forms import modelformset_factory
from django.forms import formset_factory
def editServer(request):
result = serverConfig.objects.values()
myConfigs = [entry for entry in result]
finalFormSet = modelformset_factory(serverConfig, exclude=('id',), extra=0)
#other lines
return render(request, 'limboHtml/ServerConfiguration.html', {'formset': finalFormSet, 'SubmitMessage': '', 'CurrentConfigs': myConfigs})
forms.py:
class serverForm(ModelForm):
class Meta:
model = serverConfig
fields = ['config_key', 'config_value']
def __init__(self, *args, **kwargs):
super(serverForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['config_key'].widget.attrs['readonly'] = True
self.fields['config_key'].widget.attrs['disabled'] = True
and models.py:
class serverConfig(models.Model):
config_key = models.CharField(max_length=63)
config_value = models.CharField(max_length=63)
I tried using finalFormSet = formset_factory(serverForm, extra=0) at one point, but then I just got no content in the html...

As described in the formset documention you must add the form tag manually. This is not very different from what you do when displaying a single form.
It appears that you are iterating through the formset and displayig them one by one. That means you must also add the management form
<form method="post" action="">
{{ formset.management_form }}
<div ='container'>
{% for form in formset %}
<div class='row'>{{form}} <input type="submit" value="Update" /></div>
{% endfor %}
<div>
</form>
Or you will get errors about a missing or misconfigured management form.

Note that it does not include the tags, or a submit button. We’ll have to provide those ourselves in the template.
Read more: Working with Forms: Building a form in Django
The reason you are not getting the <form> tag is because from a logical point of view a form validation can be handled anywhere in your application. That's why you need to specify the form tag explicitly with the target url (good to use reverse(view_name)), method and other parameters.

Related

Need to show result on the same page - Django

I'm creating a password generator app. The app is working and stores the value on db.
The problem is whenever I refresh, the form resubmits and takes the previous value and stores.
Also, I want to show the email and password on the same page.
Whenever I refresh, I want to show an empty form with empty fields.
Views.py
def home(request):
if request.method=='POST':
inputemail = request.POST.get('InputEmail')
gen = ''.join(random.choices((string.ascii_uppercase+string.ascii_lowercase+string.digits+string.punctuation), k=10))
newpass = Item(email=inputemail,encrypt=gen)
newpass.save()
return render(request,'home.html',{"gen":gen})
return render(request,'home.html',{})
Home.html
<form method = 'post' id='pass-form' >
{% csrf_token %}
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<input type="email" class="form-control" name="InputEmail" >
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
</div>
<button type="submit" name = "submit" class="btn btn-primary">Generate Password</button><br><br>
</form>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Generated Password</label>
<input type="text" id="InputPassword" name = "genpassword" value = {{gen}} >
</div>
Urls.py
urlpatterns = [
path('',views.home,name='home'),
]
According to docs:
you should always return an HttpResponseRedirect after successfully dealing with POST data. This tip isn’t specific to Django; it’s good web development practice in general.
So you should make another page to show generated password, which will take submitted instance id of Item model created in home view so:
def home(request):
if request.method=='POST':
inputemail = request.POST.get('InputEmail')
gen = ''.join(random.choices((string.ascii_uppercase+string.ascii_lowercase+string.digits+string.punctuation), k=10))
newpass = Item(email=inputemail,encrypt=gen)
newpass.save()
return redirect('success', args=(newpass.pk))
return render(request,'home.html',{})
def success(request, pk):
item_obj = get_object_or_404(Item, pk=pk)
return render(request,'success.html', {'gen':item_obj.encrypt})
urls.py
urlpatterns=[
path('',views.home,name='home'),
path('success/<int:pk>/',views.success,name='success')
]
success.html
<body>
<h2>The form is successfully submitted.</h2>
<br>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Generated Password</label>
<input type="text" id="InputPassword" name="genpassword" value={{gen}} >
</div>
Again go to password generator page.
</body>
Another possible solution
You can make email field required in Html form and then hard refresh the page after submitting the form using Javascript's submit event so the template:
<form method='POST' id='pass-form'>
{% csrf_token %}
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<input type="email" class="form-control" name="InputEmail" required>
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
</div>
<button type="submit" class="btn btn-primary">Generate Password</button><br><br>
</form>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Generated Password</label>
<input type="text" id="InputPassword" name = "genpassword" value ={{gen}} >
</div>
<script type='text/javascript'>
let form = document.getElementById('pass-form');
addEventListener('submit', (event) => {
location.reload(true); // hard refreshed
console.log('hard refreshed')
});
</script>
Note: Then also there are certain browsers like Microsoft Edge which gives pop up as Resubmit the form? in which they mention The page you're looking for used information that you entered. Returning to the page might trigger a repitition of any action you took there. Do you want to continue?
The moment you click on continue it creates duplicacy of records, so I think as docs mentions the first approach is better.

Writing a Django Form while keeping HTML styling

I have a Django form that I have created manually in order to keep the format of the styling, but I realized that the form is compromised of several inputs and manually is taking too long to change each.
I am also able to generate the form automatically using {{ form.as_p }} but I lose the HTML style format that I have below. Is there an easy way to make it instead of manually changing each input?
This is the original HTML template that I am trying to keep
</button>
<div class="form-outline mb-4">
<input
type="text"
id="businessName"
class="form-control"
name="businessName"
/>
<label class="form-label" for="typeText"
>Legal Business Name</label>
</div>
Here is the working Django form:
{% if submitted %}
Your forms has been submitted
{% else %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<!-- Submit button -->
<button
type="submit"
class="btn btn-primary btn-block mb-4"
id="btn"
>
Submit
</button>
</form>
Here is the views.py
def add_form(request):
submitted=False
if request.method == 'POST':
form = infoForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/?submitted=True')
else:
form = infoForm()
if 'submitted' in request.GET:
submitted=True
return render(request, 'template/template.html',{'form':form, 'submitted':submitted})
Here is the form
class infoForm(ModelForm):
class Meta:
model = Info
fields = ['businessName']
Here is what I have tried:
<div class="form-outline mb-4">
<input type="text" class="form-control" name="businessName" {% if form.is_bound %}value="{{ form.businessName.value }}"{% endif %}>
<label class="form-label">Legal Business Name</label>
</div>
{% for err in form.businessName.errors %}
<small class="text-danger mb-2 ml-2">{{ err }}</small>
{% endfor %}
My Question:
How to keep the same HTML styling while making it easy by using {{ form.as_p }}?
What is the required input for attributes in this case?
This will be the quickest way to apply custom styling to the django forms so that you let the django take care of processing the form while still using your preferred css styling...
Taken from my answer to: How to markup form fields with in Django
class MyForm(forms.Form):
myfield = forms.CharField(widget=forms.TextInput(attrs={'class': 'myfieldclass'}))
or
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['myfield'].widget.attrs.update({'class': 'myfieldclass'})
or
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
widgets = {
'myfield': forms.TextInput(attrs={'class': 'myfieldclass'}),
}
originally answered
EDIT 1 : Adding Label styling
1 set the class by the above mentioned method
eg:
self.fields['some_field'].widget.attrs.update({'class': 'some_class'})`
2 Select the label of that class and style them
.that_some_class label{
font-size: large;
}

Trying to use file upload without forms.py - 'InMemoryUploadedFile' object is not callable

So I'm trying to make a form with some data and an upload field. Django docs doesn't provide any good tutorial of doing this without forms.py. I don't want to use that.
I tried to adapt their tutorial with forms.py (https://docs.djangoproject.com/en/2.2/topics/http/file-uploads/) with my project but I'm getting an error.
"InMemoryUploadedFile' object is not callable"
I've tried searching it on google but I didn't find this error.
I obviously miss something, because when I used to do file uploads with Node I had to do more things, like setting file storage ect.
I just don't know how to handle this in django. So what am I missing and why do I get this error?
views.py
def incarcarecv(req):
context = {
'title': "title"
}
if req.method == 'POST':
nume = req.POST['nume']
prenume = req.POST['prenume']
telefon = req.POST['telefon']
email = req.POST['email']
CV = req.FILES['CV']
cvUpload = CV(solicitant = req.user, nume=nume, prenume=prenume, telefon=telefon, emailContact=email, CV=CV)
return render(req, "../templates/pagini/incarcare-cv.html", context)
models.py
class CV(models.Model):
solicitant = models.ForeignKey(User, on_delete=models.CASCADE)
dataUploadCV = models.DateField(auto_now_add=True)
nume = models.CharField(max_length=12)
prenume = models.CharField(max_length=12)
telefon = models.CharField(max_length=12)
emailContact = models.EmailField(max_length=40)
CV = models.FileField(upload_to='documents/%d/%m/%Y')
rezolvata = models.BooleanField(default=False)
def __str__(self):
return self.solicitant
html
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="container container-centru">
<h1 class="heading-contact">Incarca CV</h1>
{% include 'partials/_alerts.html' %}
<form action="{% url 'incarcarecv' %}" method="POST" class="form-contact" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label for="inputnume" class="email-contact">Nume</label>
<input type="text" name="nume" class="form-control" id="inputnume" aria-describedby="emailHelp" placeholder="Introdu nume">
</div>
<div class="form-group">
<label for="inputprenume" class="email-contact">Prenume</label>
<input type="text" name="prenume" class="form-control" id="inputprenume" aria-describedby="emailHelp" placeholder="Introdu prenume">
</div>
<div class="form-group">
<label for="inputtelefon" class="email-contact">Telefon</label>
<input type="text" name="telefon" class="form-control" id="inputtelefon" aria-describedby="emailHelp" placeholder="Introdu telefon">
</div>
<div class="form-group">
<label for="inputemail" class="email-contact">Email</label>
<input type="email" name="email" class="form-control" id="inputemail" aria-describedby="emailHelp" placeholder="Introdu email">
</div>
<div class="form-group">
<label for="inputcv" class="email-contact">CV</label>
<input type="file" name="CV" class="form-control" id="inputemail" aria-describedby="emailHelp">
</div>
<div class="form-group form-group-custom">
<input type="submit" value="Trimite" class="btn btn-secondary btn-block btn-login-custom">
<input type="submit" value="Resetează câmpurile" class="btn btn-secondary btn-block btn-reset-custom">
</div>
</form>
</div>
{% endblock %}
Let me translate: name = last name, prenume = first name, telefon = phone.
So how can I handle files in this situation and without using forms.py? As I said, django doesn't provide any tutorial on this.
Thanks!
In your view, you shadow the CV model, by defining a local variable named CV. Indeed, you write:
CV = req.FILES['CV']
So in this view, CV does not refer to the model CV, but to the file, later you then call the constructor of the model CV(..), but you thus call the file handler instead.
def incarcarecv(req):
context = {
'title': 'title'
}
if req.method == 'POST':
nume = req.POST['nume']
prenume = req.POST['prenume']
telefon = req.POST['telefon']
email = req.POST['email']
cv = req.FILES['CV']
cv_upload = CV(
solicitant=req.user,
nume=nume,
prenume=prenume,
telefon=telefon,
emailContact=email,
)
cv_upload.cv.save(cv.name, cv)
cv_upload.save()
return render(req, '../templates/pagini/incarcare-cv.html', context)
You will need to cv_upload.save(), since otherwise you construct a CV object, but you did not store in in the database.
That being said, I strongly advise you to use a Form, here it looks like a simple ModelForm will be sufficient. A form also can validate the input, and produce errors that you can send back to the user about what is missing.
By using the PEP-8 naming conventions, it is also less likely that such name clashes will occur.
You also should, in case of a successful POST request, redirect to a page. This is the Post/Redirect/Get web development pattern. Otherwise in case the submission was successful, if you render a page, and the user refreshes the page in the browser, the browser will make the same POST request.

Image cannot be uploaded

Image cannot be uploaded.form.is_valid() always returned Flase.I wrote in html
<main>
<form action="/app/img/" method="POST" enctype="multipart/form-data" role="form">
{% csrf_token %}
<h3>Upload Image!!</h3>
<div class="input-group">
<label class="input-group-btn">
<span class="btn-lg">
Select image
<input id="file1" type="file" name="image" accept="image/*">
</span>
</label>
</div>
<div class="col-xs-offset-2">
<input id="send" type="submit" value="Send" class="form-control">
</div>
</form>
</main>
in views.py
#require_POST
def img(request):
img_form = ImageForm(request.POST or None)
print(img_form.is_valid())
if request.method == "POST" and img_form.is_valid():
image = request.POST.get("image", "")
in forms.py
class ImageForm(forms.ModelForm):
image = forms.ImageField()
class Meta:
model = Image
fields = ('image',)
I really cannot understand why I can't sent images.form.is_valid() returned Flase means Form cannot be gotten images, right? What is wrong in my code?How should I fix this?
From the documentation:
When Django handles a file upload, the file data ends up placed in request.FILES.
You are not passing request.FILES to your form, so it never sees the uploaded file. You need to pass this to the form as follows:
img_form = ImageForm(request.POST, request.FILES)

Django form will not validate

I am working with Django forms and for some reason, this form will not validate! It submits alright, or at least the runserver shows an http post response with code 200 (ok). For some reason though, my form will not pass the is_valid test!
views.py:
def new_show(request):
if request.method == 'POST':
img_form = ImageForm(request.POST, request.FILES)
show_form = NewShowForm(request.POST)
if show_form.is_valid():
new_Show = Show()
new_Show.title=show_form.cleaned_data['title']
new_Show.body=show_form.cleaned_data['body']
new_Show.pub_date=timezone.now()
new_Show.location=show_form.cleaned_data['location']
new_Show.time=show_form.cleaned_data['time']
new_Show.save()
if img_form.is_valid():
image=Image(image=request.FILES['imageFile'])
new_Show.image_set.add(image)
return HttpResponseRedirect(reverse('shows'))
else:
return HttpResponseRedirect(reverse('shows'))
else:
show_form = NewShowForm()
img_form = ImageForm()
return render_to_response(
'shows/new_show.html',
{'show_form': show_form, 'img_form': img_form},
context_instance=RequestContext(request)
)
Here is my template snippet:
<form action="{% url "new_show" %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ show_form.non_field_errors }}</p>
<p>
<label for="title">Title:</label>
<input type="text" name="title"/>
</p>
<p>
<label for="body">Body:</label>
<textarea type="text" name="body"> </textarea>
</p>
<p>
<label for="location">Location:</label>
<input type="text" name="location"/>
</p>
<p>
<label for="time">Date:</label>
<input type="text" id="time" maxlength="25" size="25" name="time"><img src="{{ STATIC_URL }}../../static/cal.gif" width="16" height="16" border="0" alt="Pick a date">
</p>
<!-- Upload Form. Note enctype attribute! -->
{% csrf_token %}
<p>{{ img_form.non_field_errors }}</p>
<p>{{ img_form.imageFile.label_tag }}</p>
<p>
{{ img_form.imageFile.errors }}
{{ img_form.imageFile }}
</p>
<p><input type="submit" value="Add Upcoming Show"></input></p>
</form>
Here is my form Class:
class NewShowForm(forms.Form):
title=forms.CharField()
body=forms.CharField(widget=forms.TextArea)
location=forms.CharField()
time=forms.DateTimeField(required=True)
class ImageForm(forms.Form):
imageFile = forms.FileField(required=False, label='Select an Image')
Please help me!
If new_Show is a model, why not create a ModelForm instead of forms.Form?
So, instead of
class NewShowForm(forms.Form):
title=forms.CharField()
body=forms.CharField(widget=forms.TextArea)
location=forms.CharField()
time=forms.DateTimeField(required=True)
class ImageForm(forms.Form):
imageFile = forms.FileField(required=False, label='Select an Image')
why not using,
from django.forms import ModelForm
class NewShowForm(ModelForm):
class Meta:
model = NewShow
class ImageForm(ModelForm):
class Meta:
model = Image
?
Using ModelForm will ensure that form validation meets that of model. Moreover, it can cut off your code (especially line 6 to 11).
It will help to add these two lines to your view before if is_valid() to see the errors it's giving:
if request.method == 'POST':
img_form = ImageForm(request.POST, request.FILES)
show_form = NewShowForm(request.POST)
print(form.is_valid())
print(form.errors)
if show_form.is_valid():
You can paste the errors here and we can see what's the issue
Since you've put 2 Django forms together under one HTML form tag, when you submit the form on the front-end you're sending an extra field through request.POST that your NewShowForm doesn't have. If you combine both forms into a single Django form, you should be able to get this to work.

Categories