I have a Django form NameForm. I am attempting to iterate over an array of values and, for each value, have an option to display the same form. The only thing that should change with the form submission should be the associated value to which the form is displayed next to.
This becomes much clearer with an example. For some array [1,2,3] we should display:
We can then click on any open form icon, fill out the NameForm form. The resultant information, including the form_id (in this case 1, 2, or 3) should be returned to forms.py. How can I fetch the form_id variable from an instance of NameForm in index.py?
My (hacky) attempt can be found below. The problem is I don't know how to access the form_id variable I created in the NameForm object.
forms.py
class NameForm(forms.Form):
form_id = None
your_name = forms.CharField(label='Your name', max_length=3)
views.py
def index(request):
if request.method == 'POST':
form = NameForm(request.POST)
form_id = form.form_id # The problem line, as form_id is always None
if form.is_valid():
return HttpResponse( \
' '.join((str(form.cleaned_data['your_name']),form_id ))\
) #Throws TypeError as can't join None and string
else:
forms = []
vals = [1,2,3]
for val in vals:
form = NameForm()
form.form_id = val
forms.append(form)
return render(request, 'post/index.html', {'forms': forms})
index.html
{%for form in forms%}
{{form.form_id}}
<button class="button" onclick="openForm()">Open Form</button>
<div class="myForm">
<form class="form-container" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
<button class="button" onclick="closeForm()">Cancel</button>
</form>
</div>
</br>
{%endfor%}
In Django Form or Django Model, when you set a field's value to None, it is assumed that you want to ignore said field. This is helpful when creating a model that inherits from another model and you want to remove some unnecessary fields.
If you want to set value for a field on Form creation, you should pass it into __init__ method. E.g:
class NameForm(forms.Form):
form_id = forms.IntegerField(widget=forms.HiddenInput())
your_name = forms.CharField(label='Your name', max_length=3)
def __init__(self, form_id, *args, **kwargs):
self.fields['form_id'].initial = form_id
Alternatively, instead of overriding the __init__ method, you can set an initial value for form_id when you create an instance of NameForm. For example:
vals = [1,2,3]
forms = [NameForm(initial={'form_id': val}) for val in vals]
Related
I am having a problem with the following view and form. The form loads correctly however when I edit any of the fields it does not save. After a bit of debugging I think it is due to one of two things: either request.method == "POST" is evaluating to false, or form.is_valid() is evaluating to false. So potentially something wrong with my template or my clean() method? I've searched previous questions and can't find anything that helps. I've also checked my clean() method against the Django docs and think it is OK.
views.py
#login_required
def edit_transaction(request, pk):
transaction = get_object_or_404(Transaction, pk=pk)
if request.method == "POST":
form = TransactionForm(request.POST, instance=transaction)
if form.is_valid():
transaction = form.save(commit=False)
transaction.updated = timezone.now()
transaction.save()
return redirect('view_transaction_detail', pk=transaction.pk)
else:
form = TransactionForm(request=request, instance=transaction)
return render(request, 'budget/new_transaction.html', {'form': form})
forms.py
class TransactionForm(forms.ModelForm):
class Meta:
model = Transaction
fields = ('title', 'transaction_type', 'category', 'budgeted_amount', 'actual_amount', 'date', 'comments',)
#new_category field to allow you to add a new category
new_category = forms.CharField(max_length=30, required=False, label="New Category Title")
def __init__(self, request, *args, **kwargs):
super(TransactionForm, self).__init__(*args, **kwargs)
#category is now not a required field because you will use category OR new_category
self.fields['category'].required=False
#set to allow use of self.request.user to set user for category
self.request = request
def clean(self):
category = self.cleaned_data.get('category')
new_category = self.cleaned_data.get('new_category')
if not category and not new_category:
# raise an error if neither a category is selected nor a new category is entered
raise forms.ValidationError('Category or New category field is required')
elif not category:
# create category from new_category
category, created = Category.objects.get_or_create(title=new_category, defaults={'user': self.request.user})
self.cleaned_data['category'] = category
return super(TransactionForm, self).clean()
template
{% extends 'budget/base.html' %}
{% block content %}
<h2>New transaction</h2>
<h4>To add a new category, leave Category blank and enter your new category in the New Category Title field</h4>
<form method="POST" class="post-form">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
{% endblock %}
update following answer - accessing request through kwargs
def __init__(self, *args, **kwargs):
super(TransactionForm, self).__init__(*args, **kwargs)
self.fields['category'].required=False
self.request = kwargs.pop('request', None)
As I mentioned on your last question, since you've changed the signature of the form's init method you need to pass the request both times you instantiate it. You're only doing so when it is not POST; so, when it is a POST, Python takes the data that you passing and assigns it to the request argument, leaving the data itself blank.
form = TransactionForm(request, data=request.POST, instance=transaction)
Note it is precisely for this reason that is is a bad idea to change the signature; instead, pass request as a keyword argument and inside the method get it from kwargs.
My form.py:
class BannerForm(forms.ModelForm):
name = forms.CharField(max_length=32)
#Affiliazione = forms.CharField(disabled = True, initial='red') #in original question
#affiliation = forms.ModelChoiceField(Affiliation.objects.all(),
#widget=forms.HiddenInput(), initial=Affiliation.objects.get(id=1)) #in original question
Affiliazione = forms.CharField(disabled = True, required=False) #added after first answer
affiliation = forms.ModelChoiceField(Affiliation.objects.all(),
widget=forms.HiddenInput()) #added after first answer
The 'Affiliazione' field display 'red' but it isn't saved because Disabled controls cannot be successful. The 'affiliation' field actually pass the data but is hidden. They together give what I want (a disabled field that pass data so the user can see the value but can't change it).
The problem is I don't like to hardcode that values ('red' and 'id=1'). I have the 'Options' class in models where I choose the value to pass but I don't know how... I think it's a silly question, sorry, but someone can help me?
My models.py:
class Options(models.Model):
new_affiliation = models.ForeignKey('Affiliation')
class Affiliation(models.Model):
name = models.CharField(max_length=32, unique=True)
def __str__(self):
return self.name
class Banner(models.Model):
name = models.CharField(max_length=32, unique=True)
affiliation = models.ForeignKey(Affiliation)
Edit. My View.py:
def add_banner(request):
# A HTTP POST?
if request.method == 'POST':
form = BannerForm(request.POST)
print('form is post') #control
# Have we been provided with a valid form?
if form.is_valid():
print('form is valid') #control
# Save the new banner to the database.
banner = form.save(commit=False)
#some irrilevant code here
form.save(commit=True)
print('form salvato') #control
# Now call the homepage() view.
# The user will be shown the homepage.
return homepage(request)
else:
# The supplied form contained errors - just print them to the terminal
print (form.errors)
else:
# If the request was not a POST, display the form to enter details
#form = BannerForm(request.POST) #in original question
#initial_value = 'red' #added after first answer
#init = Affiliation.objects.get(id=1) #added after first answer
form = BannerForm(request.POST or None, initial={
'affiliation': Campaign_Options.new_regent_affiliation}) #added after first answer
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
print ('fine')
return render(request, 'core/add_banner.html', {'form': form})
My add_banner.html:
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
{{ field.errors }}
{{ field.label }}
{{ field }}
{{ field.help_text }}
<br />
{% endfor %}
Even if I don't quite get the intention of your form, but for the sake of answering your question, you could pass the initial value from your views when you initialize the form, this will make the value flexible:
def your_view(request):
# get the string for affilizione by applying your logic here
# initial_value = 'red'
form = BannerForm(request.POST or None, initial={'Affiliazione': initial_value})
I have a form with radio buttons and text fields. When I submit the form, the boolean field does not get created in the record. The boolean field is supposed to be updated via the radio buttons. What could be the issue here?
Here is the relevant part of my forms.py file:
CHOICES = (
(1,'yes'),
(0,'no')
)
class ServiceForm(forms.ModelForm):
one_time_service = forms.ChoiceField(required = True, choices = CHOICES, widget=forms.RadioSelect())
class Meta:
model = Service
fields = ('one_time_service')
This is my models.py one_time_service field
one_time_service = models.BooleanField(default=False)
This is my views.py:
def create(request):
if request.POST:
form= ServiceForm(request.POST)
if form.is_valid():
service_obj = form.save(commit=False)
service_obj.user_id = request.user.id
service_obj.save()
return render_to_response('services/service_created.html',
{'service': Service.objects.get(id=service_obj.id)})
else:
form = ServiceForm()
args= {}
args.update(csrf(request))
args['form'] = form
return render_to_response('services/create_service.html', args )
Edit: Here is my create_service.html
<form action="/services/create" method="post" enctype="multipart/form-data">{% csrf_token %}
<ul>
{{form.as_p}}
</ul>
<input type="submit" name="submit" value="Create Service">
</form>
I have no idea if this is the problem, but the line:
fields = ('one_time_service')
is wrong. That's not a single element tuple, that's a string with parens around it. Add a comma to make it a tuple:
fields = ('one_time_service',)
Edit: also, form.save() does not update any database records -- it creates a new one! That may be your problem.
Consider a model:
class MyModel(models.Model):
token = models.CharField(unique=True, db_index=True, max_length...)
name = models.CharField(...)
...
(Aside: The purpose of the token is to be an alternative to displaying and using the ID in URLs; it is not the primary key.)
And its form:
class MyForm(forms.ModelForm):
...
class Meta:
model = models.MyModel
fields = '__all__' # Django 1.6
And its template:
...
<form action={% url 'create_or_edit_mymodel' %} ...>{% csrf_token %}
{{ form.token.as_hidden }}
<label for="id_name">Name:</label>
{{ form.name }}
...
And, finally, its view:
def create_or_edit_mymodel(request, token=None):
# [A] Entering via a hyperlink with the token, editing existing model
if token:
m = models.MyModel.objects.filter(token=token).first()
form = forms.MyForm(instance=m)
# [B] Postback from form
elif request.POST:
form = forms.MyForm(request.POST)
# [C] Entering via a hyperlink without the token, creating new model
else:
m = create_new_mymodel(...) # somewhere else
form = forms.MyForm(instance=m)
if request.method == 'POST' and form.is_valid():
saved = form.save()
# Determine if 'Save' or 'Save & Close' was clicked... assume 'Save'...
form = forms.MyForm(instance=saved)
return shortcuts.render(request, '...', { 'form': form }, context_instance=RequestContext(request))
This doesn't work. The problem is that the model's ID doesn't seem to be available to Django, so entering the view at [A] populates the form with everything as expected, but clicking 'Save' and entering the view at [B] attempts to save a model with no ID, and the unique constraint on the 'token' field fires.
I tried adding the id field to the form:
{{ form.id.as_hidden }} # or...
{{ form.pk.as_hidden }}
But nothing gets rendered.
That view looks pretty uncomfortable to me, so I'm hoping I'm making this harder than it needs to be.
Here you should pass both request.POST and instance to form init:
# [B] Postback from form
elif request.POST:
form = forms.MyForm(request.POST, instance=instance)
Starting to beat my head against the wall...perhaps I am missing something simple.
models.py
class GFImage(models.Model):
image = models.ImageField(upload_to = 'uploads', null=True, blank=True)
views.py
def addImage(request):
errors = []
if request.method == 'POST':
form = ImageForm(request.POST, request.FILES)
if form.is_valid():
form.save()
urlRedirect = "/home"
return redirect(urlRedirect)
else:
form = ImageForm()
return render(request, "/add_image.html", {'form': form})
forms.py
class ImageForm(ModelForm):
class Meta:
model = GFImage
add_image.html
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type = "submit" value = "Submit">
</form>
Whatever I do, my form will not use the ClearableFileInput widget. It should default automatically, but even assigning it in the form's META will not work. What else could be blocking Django from using the clearable widget?
The ClearableFileInput will only display the clear checkbox when there's an initial file selected. Looking at your form, it looks like a a new form without initial data, so the checkbox won't be displayed.
def render(self, name, value, attrs=None):
.. snip ..
if value and hasattr(value, "url"):
template = self.template_with_initial
substitutions['initial'] = format_html(self.url_markup_template,
https://github.com/django/django/blob/5fda9c9810dfdf36b557e10d0d76775a72b0e0c6/django/forms/widgets.py#L372