Django: creating number of forms dynamically - python

so I want to create a dynamic number of forms depending on a variable.
Here is my forms.py
class CTestform(forms.Form):
def __init__(self, c_test_tokens, *args, **kwargs):
super(CTestform, self).__init__(*args, **kwargs)
wordsbeforegap = ''
iteratorforgaps = 0
for i in (0, len(c_test_tokens)-1):
if '#GAP#' not in c_test_tokens[i]:
wordsbeforegap = wordsbeforegap + c_test_tokens[i]
else:
self.fields[iteratorforgaps] = forms.CharField(widget=forms.TextInput(attrs={'size': '5'}),
required=False, label=wordsbeforegap, label_suffix='')
wordsbeforegap = ''
iteratorforgaps += 1
Here is my views.py where I call CTestform to render:
def ctest(request):
# this function is not really important for the question
# c_test_tokens = list of chars
c_test_tokens, gaps, tokenindexe = generate_c_test(exampletext())
form = CTestform(c_test_tokens=c_test_tokens)
return render(request, 'ctest.html', {'form': form})
I thought that the forms created would be in self.fields, so to print the forms at my website I have in my template this:
<div class="ctest">
{% for forms in form.fields %}
{{ forms }}
{% endfor %}
</div>
But the site is empty and no forms are getting rendered. What could be the problem?

Solved:
Problem was in my for loop
Missed "range" lol
for i in range (0, len(c_test_tokens)-1):

Related

How to get template variables by using FormView?

I am currently following Mozilla's Django tutorial (https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms). The tutorial mostly shows how to create form using functions. I am trying to make the same function view work by using a generic class view (FormView). I am able to make most of the code to work except for 2 things. First one is that I can't seem to be able to save the due date. And, second one, is that I don't know how to access the model fields in my template using template variables.
Here is my form model from the forms.py file.
class RenewBookModelForm(ModelForm):
def clean_due_back(self):
data = self.cleaned_data['due_back']
# Check if a date is not in the past.
if data < datetime.date.today():
raise ValidationError(ugettext_lazy(
'Invalid date - renewal in past'))
# Check if a date is in the allowed range (+4 weeks from today).
if data > datetime.date.today() + datetime.timedelta(weeks=4):
raise ValidationError(ugettext_lazy(
'Invalid date - renewal more than 4 weeks ahead'))
# Remember to always return the cleaned data.
return data
class Meta:
model = BookInstance
fields = ['due_back']
labels = {'due_back': ugettext_lazy('New renewal date')}
help_texts = {'due_back': ugettext_lazy(
'Enter a date between now and 4 weeks (default 3).')}
The form model implemented as a function:
#permission_required('catalog.can_mark_returned')
def renew_book_lirarian(request, pk):
book_instance = get_object_or_404(BookInstance, pk=pk)
# If this is a POST request then process the Form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = RenewBookModelForm(request.POST)
# Chech if the form is valid
if form.is_valid():
# process that data in form.cleaned_data as required (here we just write to the model due_back field)
book_instance.due_back = form.cleaned_data['due_back']
book_instance.save()
# redirect to a new URL
return HttpResponseRedirect(reverse('all-borrowed'))
# If this is a GET (or any other method) create the default form.
else:
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
form = RenewBookModelForm(initial={'due_back': proposed_renewal_date})
context = {
'form': form,
'book_instance': book_instance
}
return render(request, 'catalog/book_renew_librarian.html', context=context)
This is my class-based view from my views.py file:
class RenewBookLibrarian(LoginRequiredMixin, PermissionRequiredMixin, generic.FormView):
"""Generic class-based view for forms."""
template_name = 'catalog/book_renew_librarian.html'
permission_required = 'catalog.can_mark_returned'
form_class = RenewBookModelForm
success_url = reverse_lazy('all-borrowed')
def get_initial(self):
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
initial = {'due_back': proposed_renewal_date}
return initial
And finally this is my template file where I wish to access the model fields:
{% extends 'base_generic.html' %}
{% block content %}
<h1>Renew: {{ book_instance.book.title }}</h1>
<p>Borrower: {{ book_instance.borrower }}</p>
<p {% if book_instance.is_overdue %} class="text-danger" {% endif %}>Due date: {{ book_instance.due_back }}</p>
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="submit">
</form>
{% endblock %}
The book_instance variable in the template is not working, hence I would like to know how I can display fields from my BookInstance model.
To add book_instance to the template context, you can override get_context_data.
In the FormView, instead of checking if form.is_valid(), you override the form_valid method (see the class based view docs for basic forms).
class RenewBookLibrarian(LoginRequiredMixin, PermissionRequiredMixin, generic.FormView):
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['book_instance'] = get_object_or_404(BookInstance, pk=self.kwargs['pk'])
def form_valid(self, form):
book_instance = get_object_or_404(BookInstance, pk=self.kwargs['pk'])
book_instance.due_date = form.cleaned_data['due_date']
book_instance.save()
return super().form_valid(form) # will redirect to the success url

Issue with Django form POST method

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.

Pass choices from views to form

I use the form Bannerform to create new Banner object trough the add_banner views (and template). I use the class Options to definite which affiliation objects to permit in the form Bannerform (affiliation field).
I'm trying to pass choices from the views to the form but it give me ValueError: too many values to unpack (expected 2)
My code worked when new_affiliation was a ForeignKey but now I need more values. I think I must definite 'choices' in the views or I will have problem on the first migration (it appear to call the database tables from the models.py but not from the views.py, so if I put Options.objects.get(id=1) on the models.py it give error because the tables don't exist yet).
My form.py:
from django import forms
from core.models import Options, Banner, Affiliation #somethings other
class BannerForm(forms.ModelForm):
name = forms.CharField(max_length=32)
affiliation = forms.ChoiceField('choices')
#affiliation = forms.ModelChoiceField('choices') #same error
class Meta:
model = Banner
exclude = (#some fields)
My models.py:
from django.db import models
from django.contrib.auth.models import User
from django import forms
class Options(models.Model):
new_affiliation = models.ManyToManyField('Affiliation')
#new_affiliation = models.ForeignKey('Affiliation') #this worked (with some changes in views)
class Affiliation(models.Model):
name = models.CharField(max_length=32, unique=True)
class Banner(models.Model):
name = models.CharField(max_length=32, unique=True)
affiliation = models.ForeignKey(Affiliation)
My views.py:
def add_banner(request):
if request.method == 'POST':
#some code here
else:
options = Options.objects.get(id=1)
print(options.new_affiliation.all()) #controll
choices = options.new_affiliation.all()
print(choices) #controll
form = BannerForm(choices, initial={
#some code regarding other fields
})
return render(request, 'core/add_banner.html', {'form': form})
My add_banner.html:
<form role="form" id="banner_form" enctype="multipart/form-data "method="post" action="../add_banner/">
{% 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 %}
Any help will be apreciated.
Updated. I changed only views.py:
def add_banner(request):
if request.method == 'POST':
#some code here
else:
options = Options.objects.get(id=1)
print(options.new_affiliation.all()) #controll
choices = tuple(options.new_affiliation.all())
print(choices) #controll
form = BannerForm(choices, initial={
#some code regarding other fields
})
return render(request, 'core/add_banner.html', {'form': form})
But still give error.
Update 2. If I pass choices directly from form.py it works:
My views.py:
def add_banner(request):
if request.method == 'POST':
#some code here
else:
form = BannerForm(request.POST or None, initial={
#some code regarding other fields
})
return render(request, 'core/add_banner.html', {'form': form})
My forms.py:
class BannerForm(forms.ModelForm):
options = Options.objects.get(id=1)
choices = options.new_affiliation.all()
name = forms.CharField(max_length=32)
affiliation = forms.ModelChoiceField(choices)
Unluckly this give problems on the first migration (see above).
I'm trying to pass choices using some init method...
My forms.py:
class BannerForm(forms.ModelForm):
name = forms.CharField(max_length=32)
affiliation = forms.ModelChoiceField(choices)
def __init__(self, *args, **kwargs):
options = Options.objects.get(id=1)
choices = options.new_affiliation.all()
#choices = kwargs.pop('choices')
super(RegentForm, self).__init__(*args, **kwargs)
self.fields['affiliation'] = choices
but it say that choices is not definite
From what I can see here, it looks like you are getting an error "too many values to unpack" because you are not sending "choices" as the correct type. A ChoiceField takes choices only as a tuple, as seen in the documentation for models. If you are looking to define choices based on a QuerySet, you'll have to convert it into a tuple which can be interpreted as valid "choices". In one of my projects for example, I needed to prepare a set of years as a tuple so that I could allow users to select from a list of pre-determined years. I specified the following function to do this:
def years():
response = []
now = datetime.utcnow()
for i in range(1900, now.year + 1):
response.append([i, str(i)])
return tuple(response)
Since tuples are meant to be immutable, it usually isn't a good idea to cast them, just based on principle. However, in this case it seems necessary as a measure to declare that you are okay with the possible variability with these statements.
In your specific situation, you might consider doing something like this:
choices = tuple(options.new_affiliation.all().values())
I have not tested this code, and I frankly am not completely familiar with your project and may be mistaken in some part of this response. As a result, it may require further tweaking, but give it a try. Based on your error, this is definitely where the program is breaking currently. Update here if you make any progress.
Done!
My form.py:
class BannerForm(forms.ModelForm):
name = forms.CharField(max_length=32, label='Nome')
def __init__(self, *args, **kwargs):
options = Options.objects.get(id=1)
choices = options.new_affiliation.all()
super(BannerForm, self).__init__(*args, **kwargs)
self.fields['affiliation'] = forms.ModelChoiceField(choices)
self.fields['affiliation'].initial = choices
class Meta:
model = Banner
My views.py:
def add_banner(request):
if request.method == 'POST':
#some code here
else:
form = BannerForm(request.POST or None, initial={
#some code here
})
return render(request, 'core/add_banner.html', {'form': form})
Thank you

Django ImageField not rendering ClearableFileInput

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

Django Wizard, multiple forms in one step

In documentation of Django Wizard i found code like this:
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form }}
{% endfor %}
{% else %}
{{ wizard.form }}
{% endif %}
So I am wondering how can i add multiple forms to single step of wizard
Make one of your forms a Formset containing the rest of the forms you need. You don't need to necessarily use a ModelFormset, you can subclass the base class and create the forms manually.
This is now deprecated use this link: https://github.com/vikingco/django-formtools-addons
I wanted to share my settings if would be any help to anyone:
class BaseImageFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseImageFormSet, self).__init__(*args, **kwargs)
self.queryset = Images.objects.none()
ImageFormSets = modelformset_factory(Images, formset=BaseImageFormSet, fields=('picture',), extra=2)
form_list = [("step1", CategoryForm),
("step2", CityForm),
("step3", (
('lastform', LastForm),
('imageform', ImageFormSets)
))
]
templates = {"step1": "create_post_category.html",
"step2": "create_post_city.html",
"step3": "create_post_final.html"}
class OrderWizard(SessionMultipleFormWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos'))
def get_template_names(self):
return [templates[self.steps.current]]
def render(self, forms=None, **kwargs):
forms = forms or self.get_forms()
context = self.get_context_data(forms=forms, **kwargs)
#print(forms[1](queryset = Images.objects.none()))
return self.render_to_response(context)
def done(self, form_list, form_dict, **kwargs):
form_data_dict = self.get_all_cleaned_data()
#print(form_data_dict)
result = {}
instance = Post()
#print(form_dict)
for key in form_dict:
form_collection = form_dict[key]
#print(form_collection)
for key in form_collection:
form = form_collection[key]
print('printing form %s' % key)
#if isinstance(form, forms.ModelForm):
if key == 'lastform':
post_instance = form.save(commit=False)
nodes = form_data_dict.pop('nodes')
city = form_data_dict.pop('city')
post_instance.save()
post_instance.category.add(nodes)
post_instance.location.add(city)
print('lastfome as esu ')
if key == 'imageform':
for i in form_data_dict['formset-step3']:
picture = i.pop('picture')
images_instance = Images(post=post_instance, picture=picture)
images_instance.save()
return render_to_response('create_post_done.html', {
'form_data': result,
#'form_list': [form.cleaned_data for form in form_list],
})
I've implemented an extension to the Django Wizard, which supports multiple Forms in one wizard step:
https://pypi.python.org/pypi/django-multipleformwizard

Categories