How to create dynamic forms in Django? [duplicate] - python

I'm working on something like an online store. I'm making a form in which the customer buys an item, and she can choose how many of these item she would like to buy. But, on every item that she buys she needs to choose what its color would be. So there's a non-constant number of fields: If the customer buys 3 items, she should get 3 <select> boxes for choosing a color, if she buys 7 items, she should get 7 such <select> boxes.
I'll make the HTML form fields appear and disappear using JavaScript. But how do I deal with this on my Django form class? I see that form fields are class attributes, so I don't know how to deal with the fact that some form instance should have 3 color fields and some 7.
Any clue?

Jacob Kaplan-Moss has an extensive writeup on dynamic form fields:
http://jacobian.org/writing/dynamic-form-generation/
Essentially, you add more items to the form's self.fields dictionary during instantiation.

Here's another option: how about a formset?
Since your fields are all the same, that's precisely what formsets are used for.
The django admin uses FormSets + a bit of javascript to add arbitrary length inlines.
class ColorForm(forms.Form):
color = forms.ChoiceField(choices=(('blue', 'Blue'), ('red', 'Red')))
ColorFormSet = formset_factory(ColorForm, extra=0)
# we'll dynamically create the elements, no need for any forms
def myview(request):
if request.method == "POST":
formset = ColorFormSet(request.POST)
for form in formset.forms:
print "You've picked {0}".format(form.cleaned_data['color'])
else:
formset = ColorFormSet()
return render(request, 'template', {'formset': formset}))
JavaScript
<script>
$(function() {
// this is on click event just to demo.
// You would probably run this at page load or quantity change.
$("#generate_forms").click(function() {
// update total form count
quantity = $("[name=quantity]").val();
$("[name=form-TOTAL_FORMS]").val(quantity);
// copy the template and replace prefixes with the correct index
for (i=0;i<quantity;i++) {
// Note: Must use global replace here
html = $("#form_template").clone().html().replace(/__prefix_/g', i);
$("#forms").append(html);
};
})
})
</script>
Template
<form method="post">
{{ formset.management_form }}
<div style="display:none;" id="form_template">
{{ formset.empty_form.as_p }}
</div><!-- stores empty form for javascript -->
<div id="forms"></div><!-- where the generated forms go -->
</form>
<input type="text" name="quantity" value="6" />
<input type="submit" id="generate_forms" value="Generate Forms" />

you can do it like
def __init__(self, n, *args, **kwargs):
super(your_form, self).__init__(*args, **kwargs)
for i in range(0, n):
self.fields["field_name %d" % i] = forms.CharField()
and when you create form instance, you just do
forms = your_form(n)
it's just the basic idea, you can change the code to whatever your want. :D

The way I would do it is the following:
Create an "empty" class that inherits from froms.Form, like this:
class ItemsForm(forms.Form):
pass
Construct a dictionary of forms objects being the actual forms, whose composition would be dependent on the context (e.g. you can import them from an external module). For example:
new_fields = {
'milk' : forms.IntegerField(),
'butter': forms.IntegerField(),
'honey' : forms.IntegerField(),
'eggs' : forms.IntegerField()}
In views, you can use python native "type" function to dynamically generate a Form class with variable number of fields.
DynamicItemsForm = type('DynamicItemsForm', (ItemsForm,), new_fields)
Pass the content to the form and render it in the template:
Form = DynamicItemsForm(content)
context['my_form'] = Form
return render(request, "demo/dynamic.html", context)
The "content" is a dictionary of field values (e.g. even request.POST would do).
You can see my whole example explained here.

Another approach: Rather than breaking the normal field initialization flow, we can override fields with a mixin, return an OrderedDict of dynamic fields in generate_dynamic_fields which will be added whenever its set.
from collections import OrderedDict
class DynamicFormMixin:
_fields: OrderedDict = None
#property
def fields(self):
return self._fields
#fields.setter
def fields(self, value):
self._fields = value
self._fields.update(self.generate_dynamic_fields())
def generate_dynamic_fields(self):
return OrderedDict()
A simple example:
class ExampleForm(DynamicFormMixin, forms.Form):
instance = None
def __init__(self, instance = None, data=None, files=None, auto_id='id_%s', prefix=None, initial=None,
error_class=ErrorList, label_suffix=None, empty_permitted=False, field_order=None,
use_required_attribute=None, renderer=None):
self.instance = instance
super().__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, field_order,
use_required_attribute, renderer)
def generate_dynamic_fields(self):
dynamic_fields = OrderedDict()
instance = self.instance
dynamic_fields["dynamic_choices"] = forms.ChoiceField(label=_("Number of choices"),
choices=[(str(x), str(x)) for x in range(1, instance.number_of_choices + 1)],
initial=instance.initial_choice)
return dynamic_fields

Related

How to make an intuitive form field for a M2M relationship with many options in Django?

Apologies if the title is poorly worded, I couldn't figure out a succinct way to describe this issue.
I've been working with a client who uses the Django form library to allow users to create/edit model instances. I've recently added a many to many relationship between model A and model B, and I want there to be an input on the model A form that allows users to connect instances of model B to it. The SelectMultiple widget used by Django by default is unintuitive, and the alternative CheckboxSelectMultiple widget seems like it wouldn't be great once there are 100s of options (which is a likely scenario). What complicates this further is that there's also a M2M relationship between model C and D, connected by a through table that determines a D instance's order on a C instance, and neither of those widgets work well with through tables.
My initial thought was to use CheckboxSelectMultiple and just order the already selected options at the top, wrapping the widget in a scroll overflow container. I unfortunately don't know how to order options in that way though, since the accepted queryset parameter is inherently unordered. For the case with the through table, ideally each checked option would have an input next to it where users could set the order of the respective instance, but I'm not sure how to fundamentally alter the widget in that way.
So, how would you advise I go about making a form field for a M2M relationship with many options? Is there a way to make my modified CheckboxSelectMultiple idea work, or is there an alternative, more efficient way to do this (possibly involving libraries)? I can't imagine I'm the first person to encounter this problem.
Here's "one I wrote earlier" Sorry, this is a bit of an info-dump. Has worked, but not fully tested. Check that it's csrf-safe if you care about that (it doesn't use a form, just picks things out of request.POST)
class GenericEditM2MView( DetailView):
#model = Model # required as per DetailView
# template_name = # as per DetailView
#m2m_fieldname = None # no longer required if unique: the name of the model's m2m field to operate on
remove_qs = None # defaults to .all() of the m2m field
success_url = '.' # defaults to displaying the modified m2m relationship unless done
done_url = None # where to go if submit="Done", defaults to success_url
#
"""template_name must define a form full of checkboxes, obtained from
{% for p in add_qs %}
<input type="checkbox" name="add" value="{{p.pk}}" > {% endfor}
{% for p in remove_qs %}
<input type="checkbox" name="remove " value="{{p.pk}}" > {% endfor}
default is to return to this same view after submit, to show that the changes
have been made. You can supply <input type="submit" name="submit" value="done" />
which will go to done_url instead of success_url
example use:
class PenStockM2MView( GenericEditM2MView):
template_name = 'playpen/edit_m2m.html'
model = PenStock
# m2m_fieldname = 'name' # works it out for itself if ony one M2M field on the model
done_url = '/playpen/OK'
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
# everything works without this __init__ provided self.m2m_fieldname is present and correct.
# if model has only one m2m field, locate it via _meta as default.
# Also check m2m_fieldname is m2m because very confusing errors later if it's not!
f = getattr(self, 'm2m_fieldname', None)
m2m_fieldnames = [
field.name for field in self.model._meta.get_fields() if field.many_to_many ]
model_name = self.model.__name__
if f and not f in m2m_fieldnames:
raise AttributeError( f'field "{f}" is not a many-to-many field in {model_name}')
if not f:
if len( m2m_fieldnames ) == 1:
self.m2m_fieldname = m2m_fieldnames[0]
else:
raise AttributeError( f'Cannot identify a unique many-to-many field in {model_name}' )
def get_add_queryset(self):
field = getattr( self.object, self.m2m_fieldname)
remove_qs = self.get_remove_queryset()
already_there = remove_qs.values_list('pk', flat=True)
return remove_qs.model.objects.exclude( pk__in = already_there) # is qs.model documented?
def get_remove_queryset(self):
if hasattr( self, 'remove_queryset'):
return self.remove_qs
remove_qs = getattr( self.object, self.m2m_fieldname)
return remove_qs.all()
def get_context_data( self, **kwargs):
context = super().get_context_data( **kwargs)
context['add_qs'] = self.get_add_queryset()
context['remove_qs'] = self.get_remove_queryset()
return context
def post( self, request, *args, **kwargs):
self.object = self.get_object()
add = request.POST.getlist('add')
remove = request.POST.getlist('remove')
add_objs = list( self.get_add_queryset().filter(pk__in=add) )
remove_objs = list( self.get_remove_queryset().filter(pk__in=remove) )
field = getattr( self.object, self.m2m_fieldname )
field.add( *add_objs)
field.remove( *remove_objs)
return HttpResponseRedirect( self.get_done_url() or self.get_success_url() )
def get_success_url(self):
return self.success_url
def get_done_url( self):
done = self.request.POST.get("submit", None)
if done == "done" and hasattr(self, 'done_url'):
return self.done_url
return None

How to put arguments in a jchart.Chart class

I am trying to make a chart in Django using the jchart module and to be able to chose which rows i want to retrieve from the db using the variable hosp from my views.py to HomeTotalStudies() (below)
views.py
from charts.chartsclass import HomeTotalStudies
def home(request):
hosp = request.user.userprofile.hospital
chart = HomeTotalStudies()
return render .....
here is the /charts/chartsclass.py
from jchart import Chart
from jchart.config import (DataSet, Axes, rgba, ScaleLabel, Tick, Tooltips,
Legend, LegendLabel, Title, Hover, InteractionModes,
ElementLine, ElementPoint, ElementRectangle)
class HomeTotalStudies(Chart):
chart_type = 'pie'
responsive = False
def get_labels(self,**kwargs):
return ["CT","MG","RF","DX"]
def get_datasets(self,**kwargs):
modality = ['CT','MG','RF','DX']
count = []
if hosp=='somestring' #this is the variable i want to get from the views.py
studies = GeneralStudyModel.objects.all()
else:
studies = GeneralStudyModel.objects.filter(equipmentattributes__institution_name__exact=hosp)
for mod in modality:
cc = studies.objects.filter(modality_type__exact=mod).count()
count.append(cc)
data = [count[i] for i in range(len(count))]
colors = [
rgba(255,99,132,1),
rgba(54,162,235,1),
rgba(255,159,64,1),
rgba(255,206,86,1)
]
return [DataSet(label="My DataSet",
data=data,
borderWidth=1,
backgroundColor=colors,
borderColor=colors)]
So, my question is, how can I pass this variable hosp from the view to the chart class so that i can make the query to the db table GeneralStudyModel and i retrieve only the rows needed?
Any one has any suggestion / idea / solution?
Thanks
Yep.
You have two possibilites:
the hard way: you make an AJAX call that returns an array, and you populate this in JavaScript. This implies to make a JSON view that returns a JSON array
the easy way: you need to use class-based-view's to make modern application and in your class you override the method get_context_data()
like this:
class GeneralStudyResultsView(generic.TemplateView):
template_name='general_study_results.html'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(GeneralStudyResultsView, self).get_context_data(**kwargs)
# Create a variable you fill
context['my_big_sql'] = GeneralStudyModel.objects.all()
return context
And from there, in your template file (this is your template file, not a JavaScript file) general_study_results.html add something like:
<script>
var myData =
{% for row in my_big_sql %}
{{ row.column }}{% if not forloop.last %},{% endif %}
{% endfor %};
</script>
And then you have all your data in your HTML file ready to be show thanks to charts.js
Just add a suitable initialiser to the chart class as follows:
class HomeTotalStudies(Chart):
def __init__(self, hosp, *args, **kwargs):
self.hosp = hosp
super().__init__(*args, **kwargs)
def get_datasets(self,**kwargs):
modality = ['CT','MG','RF','DX']
count = []
if self.hosp=='somestring' #this is the variable i want to get from the views.py
studies = GeneralStudyModel.objects.all()
else:
studies = GeneralStudyModel.objects.filter(equipmentattributes__institution_name__exact=self.hosp)
...
then in your view:
from charts.chartsclass import HomeTotalStudies
def home(request):
hosp = request.user.userprofile.hospital
chart = HomeTotalStudies(hosp='whatever')
return render .....

How to make foreign key accept field value instead of its id in django?

I have created a checkbox for content filtering of products based on category.So when the user clicks on any checkbox only the books with that category should be shown.In the view I am passing the value of checkbox field(category name) obtained from the template but upon filtering, the foreign key is expecting pk(id) instead of field value.I am getting error like this,invalid literal for int() with base 10: '<category name>'.So is it possible to make foreign key accept value instead of id?
Models.py,
class Add_cat(models.Model):
category = models.CharField("Name",max_length=25,unique=True)
def __unicode__(self):
return u'{0}'.format(self.category)
class Add_prod(models.Model):
book = models.CharField("Book Name",max_length=40)
author = models.CharField("Author",max_length=30)
price = models.PositiveIntegerField("Price")
image = models.ImageField(upload_to='images',null=True)
cat = models.ForeignKey(Add_cat,on_delete=models.PROTECT)
Template file,
{% for i in products %}
<input type="checkbox" name="cat_name" value="{{i.cat}}">{{i.cat}}<br>
{% endfor %}
Views.py,
def welcome_user(request):
if 'cat_name' in request.GET:
filter_category = request.GET.get('cat_name')
my_products = Add_prod.objects.filter(cat__in = filter_category)
context = { "products":my_products}
else:
my_products = Add_prod.objects.all()
context = { "products":my_products}
return render(request,"welcome-user.html",context)
You can check in the category field itself:
my_products = Add_prod.objects.filter(cat__category__in=filter_category)
Have a look at the documentation on how this works.
Above, is only applicable if filter_category is a list. If it is a string you can filter like following:
my_products = Add_prod.objects.filter(cat__category=filter_category)
There are two things wrong with your code
You need to look up the field rather than the foreign key
By using __in you are looking the category is equal to any one of the characters in the filter_category.
Hence to fix, use the field lookup and remove the __in
Add_prod.objects.filter(cat__category=filter_category)
You can try this,it will help you:
Add_prod.objects.filter(cat__category = filter_category)

Django hide field from forms and add automatically field

I want to add to the cart the actual product I'm in (product_detail.html).
So in the product_unit, is just needed to specify the quantity of the product.
Anyway I can't make the unit_product, automatically add the actual product I'm in.
forms.py
class Product_unitForm(forms.ModelForm):
class Meta:
model = Product_unit
fields = [
'product',
'quantity',
]
widgets = {'product': forms.HiddenInput()}
I hide the product from the template, because it is just the actual product, no need to specify.
views.py
def product_detail(request, id_category=None,id_product=None):
actual_product = Product.objects.get(id = id_product)
#Has an actual customer
#FORM
form_product_unit = Product_unitForm(request.POST or None)
form_product_unit.fields['product'] = actual_product # I try to add the product this way
if form_product_unit.is_valid():
instance_product_unit = form.save(commit=False)
instance_product_unit.product.save()
last_order = Order.objects.last()
is_buying = False
if(last_order.status == "en curso"):
is_buying = True
context = {
"Product" : actual_product,
"Is_buying" : is_buying,
#FORMS
"form_product_unit" : form_product_unit,
}
return render(request, "shopping/product_detail.html", context)
I want to manually from the views, add the product field of product_unit to the actual product it has (actual_product)
template
<img src="{{Product.image.url}}"/>
<h1>{{Product.title}}</h1>
<form method="POST" action="">{% csrf_token %}
{{ form_product_unit.as_p }}
<input type="submit" value="Add" />
</form>
In your views.py file I think you just need to make two changes
def product_detail(request, id_category=None,id_product=None):
actual_product = Product.objects.get(id = id_product)
form_product_unit = Product_unitForm(data=request.POST or None,
initial={'product': actual_product})
And also remove the line form_product_unit.fields['product'] = actual_product. You might need to play around with the initial dictionary a bit to get it to bind the correct value to the field but that's the general idea. The related section in the docs is https://docs.djangoproject.com/en/1.9/ref/forms/api/#dynamic-initial-values

Django fill up ChoiceField - Select html options

I am facing problem filling my select options using django forms. Even though I define choices, I don't see any choices.
My form in .py file:
class SignupForm(forms.Form):
countrySignup = forms.ChoiceField( required = True,
widget = forms.Select( choices = ('United States', 'Jordan') ))
my html template has the following:
<p>
<label for="countrySignup" class="youcountry" data-icon="">Your Country </label> </br>
{{ signupForm.countrySignup }}
{{ signupForm.countrySignup.errors }}
</p>
and of course my view has the following code to pass the variable to template:
def myView(request):
#
# create the unbinded forms for registration
#
if request.method == 'GET':
signupForm = regForms.SignupForm()
return render(request,
"login.html",
{'loginForm': loginForm,
'signupForm' : signupForm })
What am I missing because I don't see any options in my select , it remains empty
I missed the documentation that choices needs to be a list of tuples with 2 elements each.
choices:
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this field. This argument accepts the same formats as the choices argument to a model field. See the model field reference documentation on choices for more details.**
this solved it:
countrySignup = forms.ChoiceField( choices = [('PS', 'Palestine'),
('JD', 'Jordan')] )

Categories

Resources