Django modelformset_factory() filtering - django

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(...))

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

How to initialize a Django ModelChoiceField from a view?

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.

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 formset validating all forms

I have made a formset like this.
class MixedObsCollsForm(forms.Form):
def __init__(self, *args, **kwargs):
project_id = kwargs.pop('project_id')
super(MixedObsCollsForm, self).__init__(*args, **kwargs)
project = get_object_or_404(Project, pk=project_id)
self.fields['photos_upload'] = forms.ImageField(label="Photos", required=False)
taxon = forms.CharField(max_length=80, required=False)
growth_form = forms.ChoiceField(choices = growthFormChoice, required=False)
height = forms.DecimalField(max_value=99, min_value=0, decimal_places=2, required=True)
density = forms.IntegerField(max_value=100, min_value=1, required=False)
And I have a weird way to display it in a view using a class to handle all the processing. But its like this...
class ObservationFormView(object):
def __init__(self, request=None, project_id=None, observation_id=None):
self.request = request
self.ObservationFormSet = formset_factory(form=MixedObsCollsForm, extra=5)
self.ObservationFormSet.form = staticmethod(curry(MixedObsCollsForm, project_id=project_id))
self.project = get_object_or_404(Project, pk=project_id)
self.user = request.user
self.use_type = 'create'
self.formset = self.ObservationFormSet(self.request.POST or None)
def isValid(self):
return self.formset.is_valid() & self.locationForm.is_valid()
def render(self):
return render(self.request, 'observation_form.html', {'use_type': self.use_type, 'formset': self.formset, 'locationForm':self.locationForm, 'project_photos': self.project.photos.all(),})
def processForm(self):
for form in self.formset:
if form.is_valid() == True:
if form.cleaned_data['is_collection'] == True:
collection = self.getOrMakeCollection(form.cleaned_data, self.locationForm.cleaned_data['location'], False)
if form.cleaned_data['is_collection'] == False:
collection = self.getOrMakeCollection(form.cleaned_data, self.locationForm.cleaned_data['location'], True)
observation = self.saveObservation(form.cleaned_data, self.locationForm.cleaned_data['location'], collection)
return observation
The problem is when I render this form in a view even the forms within the formset which are empty don't validate. Reporting "This field is required." next to all the height fields. Even the empty ones.
It was my understanding that the empty forms should always pass validation. I have looked at the management form data and it all looks fine.
If I change required to False it means I end up with IntegrityErrors when I try to save the formset to the database.
I finally worked out what it was so here it is for anyone else who has this problem, its easy to overlook.
It wasn't even included in the code in the question so there wasn't much help anyone could offer.
This line:
growth_form = forms.ChoiceField(choices = growthFormChoice, required=False)
Used the growthFormChoice variable which was a tuple of choices like this.
growthFormChoice = (('0', 'Select one'),
('T', 'tree'),
('S', 'shrub'))
Because of this the default value ('0':'Select One') resulted in passing a 0 to every form which wasn't (intentionally) altered and triggering validation for the rest of the fields. Simply changing it to ('':'Select One') was all it needed.

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)