How to initialize a Django ModelChoiceField from a view? - django

I have the following model relationship where a task is associated to an objective and an objective is associated to a User. I created a Django form that displays all the objectives that are associated to a User.
class DropDownMenuSelectedObjectivesForm(forms.Form):
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('id')
super(DropDownMenuSelectedObjectivesForm, self).__init__(*args, **kwargs)
self.fields['objective'] = forms.ModelChoiceField(queryset = Objective.objects.values_list('objective',flat=True)
.filter(accounts=User.objects.get(id=user_id),
status='In Progress'), empty_label=None)
When I open my views.py I am able to see all the objectives from the User but I would like that the Django form dropdown menu could be initialized with the current objective that is associated with a task.
So far, I have tried the following to initialize the dropdown menu but I've got no success.
# views.py
def update_task(request, id):
'''Update a task'''
task = Task.objects.get(pk=id) # get the task id from the db
associated_task_objective = task.objective.values('objective')[0]
form = TaskModelForm(request.POST or None, instance=task)
# Attempt 1 to initialize the ModelChoiceField
objective = DropDownMenuSelectedObjectiveForm(id = request.user.id, initial = { 'objective': associated_task_objective})
if request.method == "GET":
template_name = 'task/formTask.html'
return render(request, template_name, {'form': form, 'objective':objective})
# forms.py
class DropDownMenuSelectedGoalsForm(forms.Form):
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('id')
super(DropDownMenuSelectedGoalsForm, self).__init__(*args, **kwargs)
# Attempt 2 to initialize the ModelChoiceField
self.fields['objective'] = forms.ModelChoiceField(queryset = Objective.objects.values_list('objective',flat=True)
.filter(accounts=User.objects.get(id=user_id),
status='In Progress'), empty_label=None, initial=2)
Even if I try to initialize the ModelChoiceField from the forms.py with a valid pk number, the modelchoicefield doesn't initialize.
Any idea or suggestion is really appreciated :)

I had to do the following to solve this problem:
def update_task(request, id):
'''Update a task'''
task = Task.objects.get(pk=id) # get the task id from the db
associated_task_objective = task.goal.values_list('goal',flat=True)[0]
form = TaskModelForm(request.POST or None, instance=task)
objective = DropDownMenuSelectedObjectiveForm(id = request.user.id, initial = { 'objective': associated_task_objective})
The initial keyword is looking by name of the objective, not by pk.

Related

How do I pass parameter to the form in Django?

So as the title states, I'm trying to pass a parameter from views to forms. I've seen several Stack Overflow posts regarding this issue, but none of them solved my problem :(
So what I'm trying to build is basically a question-and-answer application. Each user answers to pre-provided questions. I want to make an answering template for each corresponding question. So here's my code:
forms.py
class AnswerForm(forms.ModelForm):
main_answer = formsCharField(required=True)
# Some other fields
def __init__(self, *args, **kwargs):
self.q = kwargs.pop('q')
super(AnswerForm, self).__init__(*args, **kwargs)
self.fields['main_answer'].label = q.main_question
views.py
def createAnswer(request, pk):
thisQuestion = Question.objects.get(question_number=pk)
AnswerFormSet = modelformset_factory(Answer, form=AnswerForm(q = thisQuestion), extra=1)
formset = AnswerFormSet(queryset=Answer.objects.filter(authuser=request.user.id, question_number=pk))
# And some other codes
I'm supposed to show this formset in my template using {% crispy_formset%}. However I keep getting this error: "name 'q' is not defined". What is the problem here?
You pass these when you construct the FormSet with the form_kwargs=… parameter [Django-doc]:
def createAnswer(request, pk):
thisQuestion = Question.objects.get(question_number=pk)
AnswerFormSet = modelformset_factory(Aswer, form=AnswerForm, extra=1)
formset = AnswerFormSet(
form_kwargs={'q': thisQuestion},
queryset=Answer.objects.filter(authuser=request.user.id, question_number=pk)
)
You also will get a name error, because you use q in the __init__ method, but q is not defined:
class AnswerForm(forms.ModelForm):
main_answer = formsCharField(required=True)
# Some other fields
def __init__(self, *args, **kwargs):
# ↓ assign to a local variable q
q = self.q = kwargs.pop('q')
super(AnswerForm, self).__init__(*args, **kwargs)
self.fields['main_answer'].label = q.main_question

copy values of multiple ModelMutipleChoiceField into one after Post using MPTT

I've been struggling with this issue all day and hope someone can help.
I have all my hierarchies classified by category in the same table.
during the form creation, I want to separate each hierarchy by category and render it using a ModelMutipleChoiceField his way not all hierarchies are displayed together.
The problem comes when the form is submitted, as I need to go through each ModelMutipleChoiceField field and get the selected values and copy these to the model field before saving the form. however, I am not able to iterate through the ModelMutipleChoiceField and get the selected values. I also don't know how to set these values on the ModelField
NOTE: The number of hierarchies can vary.
here is my code:
I'm using Django MPTT and create my hierarchy structure using 2 models.
one is the category(Hierarchy) and the other is the nodes of the hierarchy (HierarchyNode_MPTT).
Then I created a separate model that has ManyToManyField pointing to the HierarchyNode_MPTT.
Models.py
class Hierarchy(models.Model):
ID = kp.ObjectIDField()
name = kp.ObjectNameField()
ext_hierarchy = kp.ObjectTechnicalID()
seq_no = kp.SeqNoField(unique=True)
mptt_seq_no = models.PositiveIntegerField()
class HierarchyNode_MPTT(MPTTModel):
id = kp.ObjectIDField()
name = kp.ObjectNameField()
description = kp.ObjectDescriptionField()
ext_node_id = kp.ObjectShortNameField()
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
hierarchy = models.ForeignKey(Hierarchy, on_delete=models.CASCADE, null=True, blank=True, related_name='children')
class Configuration(models.Model):
uuid = kp.ObjectIDField()
name = kp.ObjectNameField()
description = kp.ObjectDescriptionField()
hierarchy_nodes = models.ManyToManyField(HierarchyNode_MPTT)
Then I created the form and implement the init method to automatically create as many hierarchies as I need.
form.py
class ConfigurationCreateForm(forms.ModelForm):
class Meta:
model = ForecastConfiguration
exclude = ['uuid', 'hierarchy_nodes']
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
hierarchies = Hierarchy.objects.all()
for hierarchy in hierarchies:
field_name = 'hierarchy_%s' % (hierarchy.mptt_seq_no,)
self.fields[field_name] = TreeNodeMultipleChoiceField(queryset=HierarchyNode_MPTT.objects.all().filter(hierarchy=hierarchy),label=hierarchy.name, required=True)
try:
self.initial[field_name] = HierarchyNode_MPTT.objects.root_node(tree_id=hierarchy.mptt_seq_no)
except IndexError:
self.initial[field_name] = ''
def copy_hierarchies(self, *args, **kwargs):
hierarchies = Hierarchy.objects.all()
choice_list = list()
for hierarchy in hierarchies:
field_name = 'hierarchy_%s' % (hierarchy.mptt_seq_no,)
selected_values = self.cleaned_data.get(field_name)
for selection in selected_values:
choice_list.append(selection)
self.initial['hierarchy_nodes'] = choice_list
Finally, the idea was to implement the post method on the View to loop over the created hierarchies and then assign the value to the model field called 'hierarchy_nodes'
view.py
class ConfigurationCreateView(CreateView):
model = Configuration
form_class = ConfigurationCreateForm
template_name = 'frontend/base/config_create.html'
def get(self, request, *args, **kwargs):
form = ConfigurationCreateForm(user=request.user)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
form.copy_hierarchies(*args, **kwargs)
if form.is_valid():
fcc_form = form.save(commit=True)
messages.add_message(self.request, messages.INFO, 'Your Forecast Configurations has been saved')
return redirect(reverse('planning_detail', kwargs={'uuid': self.fcc_form.uuid}))
else:
messages.add_message(self.request, messages.ERROR, 'Error when creating the Forecast Configuration')
return render(request, self.template_name, {'form': form})
As you can see I created a method in my form called copy_hierarchies which is where I was planning to copy the hierarchy values, this is the method where I'm having problems.
if there is an easier way to perform this using Javascript, I'm open to these options.
Thanks in advance.
I wasn't able to solve this using multi-choice field, however, the following is the solution for a ChoiceField (single selection)
1) Changed my view.py post method to save the object.
2) After the model is saved I loop over the request input filed and append the values to the created instance.
3) Save the instance.
4) delete my copy_hierarchies method in forms.py
here is the code snippet created in views.py
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
fcc = form.save()
for key in self.request.POST:
# check only the ones w/ 'hierarchy_#'
if key.startswith('hierarchy_'):
# get form field object
id = self.request.POST[key]
node = HierarchyNode_MPTT.objects.get(id=id)
# add to object instance
fcc.hierarchy_nodes.add(node)
fcc.save()

Django - MultipleCheckBoxSelector with m2m field - How to add object instead of save_m2m()

I use inlineformset_factory with a custom form option in order to change the queryset and the widget of a m2m field, ie: ezMap. I want the form to give the user the option to add or remove the current selected_map to the m2m field with CheckBoxSelectMultiple widget. However, I dont want to give the user the ability to remove other objects that were already there. The problem is when I save the formset with formset.save_m2m(), it overides the field and erase all objects that were already saved.
How could I just add a new object without erasing others?
models: (some of unecessary fields were removed)
class Shapefile(models.Model):
filename = models.CharField(max_length=255)
class EzMap(models.Model):
map_name = models.SlugField(max_length=50)
layers = models.ManyToManyField(Shapefile, verbose_name='Layers to display', null=True, blank=True)
class LayerStyle(models.Model):
styleName = models.SlugField(max_length=50)
layer = models.ForeignKey(Shapefile)
ezMap = models.ManyToManyField(EzMap)
forms:
class polygonLayerStyleFormset(forms.ModelForm):
add_to_map = forms.BooleanField(required=False)
def __init__(self, *args, **kwargs):
self.map_selected = kwargs.pop("map_selected", None)
super(polygonLayerStyleFormset, self).__init__(*args, **kwargs)
self.fields['conditionStyle'].help_text = "Put * if you want to select the entire table"
self.fields['ezMap'].widget = forms.CheckboxSelectMultiple()
self.fields['ezMap'].queryset = EzMap.objects.filter(id=self.map_selected.id)
self.fields['ezMap'].help_text =""
class Meta:
model = LayerStyle
def save(self, *args, **kwargs):
instance = super(polygonLayerStyleFormset, self).save(*args, **kwargs)
instance.add_to_map = self.cleaned_data['add_to_map']
return instance
ftlStylePolygonFormset = inlineformset_factory(Shapefile, LayerStyle, can_delete=True, extra=1, max_num=5,
fields = ['styleName', 'conditionStyle', 'fillColor', 'fillOpacity', 'strokeColor', 'strokeWeight', 'ezMap'], form=polygonLayerStyleFormset)
views:
def setLayerStyle(request, map_name, layer_id):
map_selected = EzMap.objects.get(map_name=map_name, created_by=request.user)
layer_selected = Shapefile.objects.get(id=layer_id)
layerStyle_selected = LayerStyle.objects.filter(layer=layer_selected)
styleFormset = ftlStylePolygonFormset
if request.POST:
formset = styleFormset(request.POST, instance=layer_selected)
if formset.is_valid():
instances = formset.save()
for instance in instances:
if instance.add_to_map:
instance.ezMap.add(map_selecte)
else:
instance.ezMap.remove(map_selected)
save_link = u"/ezmapping/map/%s" % (map_name)
return HttpResponseRedirect(save_link)
else:
formset = styleFormset(instance=layer_selected)
#set initial data for add_to_map
for form in formset:
if form.instance.pk:
if map_selected in form.instance.ezMap.all():
form.fields['add_to_map'].initial = {'add_to_map': True}
I am confused as to what you're doing with the ezMap form field. You set its queryset to a single-element list, then use a CheckboxSelectMultiple widget for it. Are you setting up to let the user deselect that matching map, but not add new ones?
To do this at initialization, you need to define a custom base formset class and pass that in as the formset argument to your factory.
from django.forms.models import BaseInlineFormSet
class polygonLayerStyleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.map_selected = kwargs.pop("map_selected", None)
super(polygonLayerStyleForm, self).__init__(*args, **kwargs)
self.fields['conditionStyle'].help_text = "Put * if you want to select the entire table"
self.fields['ezMap'].widget = forms.CheckboxSelectMultiple()
self.fields['ezMap'].queryset = EzMap.objects.filter(id=self.map_selected.id)
self.fields['ezMap'].help_text =""
class Meta:
model = LayerStyle
class polygonLayerStyleFormset(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
self.map_selected = kwargs.pop("map_selected", None)
super(polygonLayerStyleFormset, self).__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['map_selected'] = self.map_selected
return super(polygonLayerStyleFormset, self)._construct_form(i, **kwargs)
ftlStylePolygonFormset = inlineformset_factory(Shapefile, LayerStyle, formset=polygonLayerStyleFormset, form=polygonLaterStyleForm, # and other arguments as above
)
It might be simpler to just go through the formset forms and directly change the field's queryset after creating it in your view:
formset = ftlStylePolygonFormset(instance=layer_selected)
for form in formset.forms:
form.fields['ezMap'].queryset = EzMap.objects.filter(id=map_selected.id)
Speaking of which, the usual convention is to split the POST and GET cases in the view:
from django.shortcuts import render
def setLayerStyle(request, map_name, layer_id):
map_selected = EzMap.objects.get(map_name=map_name, created_by=request.user)
layer_selected = Shapefile.objects.get(id=layer_id)
layerStyle_selected = LayerStyle.objects.filter(layer=layer_selected)
if request.method == 'POST':
formset = ftlStylePolygonFormset(request.POST, instance=layer_selected, map_selected=map_selected)
if formset.is_valid():
instances = formset.save()
save_link = u"/ezmapping/map/%s" % (map_name)
return HttpResponseRedirect(save_link)
else:
formset = ftlStylePolygonFormset(instance=layer_selected, map_selected=map_selected)
return render(request, "ezmapping/manage_layerStyle.html", {'layer_style': layerStyle_selected, 'layerStyleformset': formset, 'layer': layer_selected})
And it's better to use the redirect shortcut to reverse lookup a view for your redirect on success rather than hardcode the target URL. And to use get_object_or_404 or some equivalent when accessing objects based on URL arguments - right now a bogus URL will trigger an exception and give the user a 500 status error, which is undesirable.
To conditionally add to the ezMap relationship:
class polygonLayerStyleForm(forms.ModelForm):
add_to_map = forms.BooleanField()
def save(self, *args, **kwargs):
instance = super(polygonLayerStyleForm, self).save(*args, **kwargs)
instance.add_to_map = self.cleaned_data['add_to-map']
return instance
Then in the view:
instances = formset.save()
for instance in instances:
if instance.add_to_map:
instance.ezMap.add(map_selected)
You could also do the add call in the save method, but then you'd have to set the map as member data sometime previously - and more importantly, deal with the commit=False case.

Django modelformset_factory() filtering

I'm needing to filter out a significant amount of objects from my query. Currently, it is grabbing all objects in the class, and I want to filter it to the relevant ones which are in a querystring. How can I do this? When I try, I get an Attribute Error stating
''QuerySet' object has no attribute '__name__'.'
The code that works, but very slowly is:
formset = modelformset_factory(Transaction, form=PaidDateForm, extra=0, can_delete=False)
Also, the formset:
formset = formset(request.POST, Transaction.objects.filter(pk__in=qs))
The QueryString that I am wanting to filter by is called 'qs.'
class PaidDateForm(forms.ModelForm):
formfield_callback = jquery_datefield
Amount",max_digits=14,decimal_places=2,required=False)
date_cleared = forms.DateField(label="Cleared Date",widget=JQueryDateWidget(), input_formats=settings.DATE_INPUT_FORMATS, required=False)
class Meta:
model = Transaction
include = ('date_time_created')
def __init__(self, *args, **kwargs):
self.queryset = Transaction.objects.filter(pk__in=qs)
super(PaidDateForm, self).__init__(*args, **kwargs)
for field in self.fields:
if field != 'date_cleared':
self.fields[field].widget = forms.HiddenInput()
self.fields['paid_amount'].widget.attrs['size'] = 12
self.initial['paid_amount'] = '%.2f' % (self.instance.usd_amount)
Look at the example in Django documentation:
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#changing-the-queryset
If I understand your question correctly there is two approach to your problem:
First:
TransactionFormset = modelformset_factory(Transaction,form=PaidDateForm, extra=0, can_delete=False)
formset = TransactionFormset(queryset=Transaction.objects.filter(pk__in=qs))
Second options is to create BaseTransactionFormset
class BaseTransactionFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseTransactionFormSet, self).__init__(*args, **kwargs)
#create filtering here whatever that suits you needs
self.queryset = Transaction.objects.filter()
formset = modelformset_factory(Transaction, formset=BaseTransactionFormSet,form=PaidDateForm, extra=0, can_delete=False)
Does this code help you?
I hesitate you still need it, and yet this code works for me
FormSet = modelformset_factory(YourModel, fields=(...))
form_set = FormSet(queryset = YourModel.objects.filter(...))

Django ModelForm with field overwrite does not post data

So, I have the following form:
class DesignItemForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
self.fields['quantity'] = forms.ChoiceField(choices=CHOICES)
class Meta:
model = DesignItem
fields = ('quantity','trackable',)
My view:
d = Design.object.get(slug=fromInput)
....
DesignItemInlineFormSet = inlineformset_factory(Design, DesignItem, fk_name="design", form=DesignItemForm,)
if request.method == "POST":
formset = DesignItemInlineFormSet(request.POST, request.FILES, instance=d)
if formset.is_valid():
formset.save()
DesignItemInlineFormSet(instance=d)
As you can tell, in my form, I overwrote the quantity field to be a drop down instead of an integer field.
For some reason, when I submit the form, the data is not updated in the database. However, if I change the form to the following, it works (of course, it doesn't have the dropdowns I want, but it posts to the db). Why is this, and how do I fix it?
class DesignItemForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
# CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
# self.fields['quantity'] = forms.ChoiceField(choices=CHOICES)
class Meta:
model = DesignItem
fields = ('quantity','trackable',)
EDIT: Here is the DesignItem model:
class DesignItem(models.Model):
"""Specifies how many of an item are in a design."""
design = models.ForeignKey(Design, related_name="items")
quantity = models.PositiveIntegerField(default=1)
trackable = models.ForeignKey(Trackable, related_name="used")
have you tried just overriding the widget instead of the whole field?
i guess you want a select widget
def __init__(self, *args, **kwargs):
super(DesignItemForm, self).__init__(*args, **kwargs)
CHOICES=[(i,i) for i in range(MAX_DESIGN_ITEM_QUANTITY)]
self.fields['quantity'].widget = forms.Select(choices=CHOICES)