Send additional data to form initialisation - django

So I'm creating a form object using request.POST data, but want to initialise additional fields using other values.
This is what i tried, it isn't working:
#forms.py
class InputForm3(forms.Form):
url = forms.URLField(required=True)
db = forms.CharField(required=False)
wks = forms.CharField(required=True, initial="Sheet1")
table = forms.CharField(required=False, initial="test_table")
def __init__(self, wks, *args, **kwargs):
self.wks=wks
super().__init__(*args, **kwargs)
self.cleaned_data = None
def clean(self):
self.cleaned_data = super().clean()
print("FORM3 cleaned_data : ", self.cleaned_data)
#views.py
form3=InputForm3(wks="Sheet1", data= request.POST)
if form3.is_valid:
#remaining code
#output
FORM3 cleaned_data : {'url': 'https://randomurl.com', 'db': 'testdb', 'table': ''}
the fields 'url' and 'db' are present directly in request.POST, HOW DO I INITIALISE THE OTHER FIELDS PLEASE HELP!

Not sure of the context of why you'd want to do that.
But maybe you can try copying the form's data after you initialized the form with the request.POST data. You'll have access to modify the copied data. For example:
If you're using a function-based view:
def view_name(request):
form3 = InputForm3(request.POST)
if request.method == "POST":
form3 = InputForm3(data=request.POST) # removing wks from the parameter here...
# assigning the copied collection of data to the form's data so we can modify it
form3.data = form3.data.copy()
form3.data['wks'] = 'Sheet1' # or whatever name you wish to name that wk
if form3.is_valid():
# remaining code
...
If you're using a class-based view:
def post(self, request, *args, **kwargs):
form3 = InputForm3(data=request.POST)
form3.data = form3.data.copy()
form3.data['wks'] = 'Sheet1'
if form3.is_valid():
# remaining code
...
That worked for me.

Related

How to access form object before and after saving in django-bootstrap-modal-forms

I have following code in my view of adding a new Item. Some fields are filled via user some fields are filled in the background. If form is valid then user is redirected to a url with a parameter (slug) from added object. How can I convert this code to django-bootstrap-modal-forms way?
def category_view(request, slug, *args, **kwargs):
...
if request.POST:
form = CreateItemForm(request.POST)
if form.is_valid():
if not request.user.is_authenticated:
raise PermissionDenied()
obj = form.save(commit=False)
obj.created_country = Constants.country_code
obj.created_by = request.user
obj.save()
return redirect('category:item_detail', slug=obj.slug)
I used django-bootstrap-modal-forms in the below way. but country and user fields are not null and must be filled. These fields are not part of the form.
class add_person(BSModalCreateView):
template_name = 'add_item.html'
form_class = CreateItemForm
success_message = 'Success: Item was created.'
success_url = reverse_lazy('category:item_detail') # slug needed
You are asking, how to modify the form and the only code you do not provide is the form. But try something like this:
forms.py
class BaseForm(forms.BaseForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
if isinstance(field.widget, widgets.RadioSelect):
continue
elif isinstance(field.widget, widgets.Select):
field.widget.attrs.update({'class': 'form-select'})
continue
field.widget.attrs.update({'class': 'form-control'})
class CreateItemForm(BaseForm):
# your code
This itereates over your FormFields and adds the bootstrap class form-select, or much more important form-control to the widget of your field.

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 Pass Request Data to Forms.py

The scenario;
We got a from with fields and inside form there is a combobox, it fills with items.
We have tenancy and every user got TenantID so when A1 user(tenantid 1) calls create form, we need to filter that combobox to filter only A1 UserItems with using Query Filtering.
Similarly for other tenants.
How can I pass that dynamic tenantid.
Btw for every user tenantid stored in abstracted class django core USER- added new field tenantid.
Any advice Im open for it, thank you for your attention.
State: Solved !
Forms.py
class ItemForm(forms.ModelForm):
class Meta:
model = Items
fields = ('id', 'item', 'start', 'end')
widgets = {
'start': DateTimePickerInput(format='%Y-%m-%d %H:%M'),
'end': DateTimePickerInput(format='%Y-%m-%d %H:%M'),
}
def __init__(self, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
self.fields['item'].queryset = Items.objects.filter(tenantid=int(User.tenantid))
views.py
#login_required()
def create_item_record(request):
if request.method == 'POST':
form = ItemForm(request.POST)
if request.method == 'GET':
tenantidX = request.user.tenantid
form = ItemForm()
return save_item_form(request, form, 'items_create_partial.html')
Just pass user from request to your form:
class ItemForm(forms.ModelForm):
class Meta:
model = Items
fields = ('id', 'item', 'start', 'end')
widgets = {
'start': DateTimePickerInput(format='%Y-%m-%d %H:%M'),
'end': DateTimePickerInput(format='%Y-%m-%d %H:%M'),
}
def __init__(self, user, *args, **kwargs):
super(ItemForm, self).__init__(*args, **kwargs)
self.fields['item'].queryset = Items.objects.filter(tenantid=int(user.tenantid))
#login_required()
def create_item_record(request):
if request.method == 'POST':
form = ItemForm(request.user, request.POST)
if request.method == 'GET':
form = ItemForm(request.user)
return save_item_form(request, form, 'items_create_partial.html')
the best and easy way of getting current request with using "django -crum" https://pypi.org/project/django-crum/ .
pip install django-crum
after that add to settings.py
# settings.py
MIDDLEWARE_CLASSES = (
'crum.CurrentRequestUserMiddleware',
...
)
include lib
from crum import get_current_request
request = get_current_request()
Then you can reach active request inside with request.user.tenantid

Complicated Django ModelForm validation with OneToOneField and customed argument

Sorry for the lengthy question. I have a complicated situation with django modelform validation. I have a model UserProject ready and created many objects. I also have another model Action_Inputs to accept multiple parameters, which is a onetoonefield relation with UserProject. I do need customed input argument for one field of Action_Inputs. But I cannot have the form valided.
models.py
class UserProject(models.Model):
pid = models.CharField(max_length=10, null=False, unique=True)
email = models.EmailField(max_length=254, null=False)
directory = models.CharField(max_length=255)
class Action_Inputs(models.Model):
userproject = models.OneToOneField(UserProject, null=False)
method = models.CharField(max_length=255)
file = models.FileField(upload_to='userdata')
Now I have the following ModelForm which takes a customed input argument jobid, catched from url, which is a string to get back to the previous UserProject pid:
class ActionInputsForm(ModelForm):
def __init__(self, jobid, *args, **kwargs):
super(ActionInputsForm, self).__init__(*args, **kwargs)
self.fields['userproject'].initial = jobid
class Meta:
model = Action_Inputs
fields = ['userproject', 'method', 'file'] # userproject will be hidden
def clean_userproject(self):
userproject = self.cleaned_data['userproject']
if len(userproject) != 10:
raise forms.ValidationError("---PID error.")
return UserProject.objects.get(pid=userproject)
def clean(self):
return self.cleaned_data
In my views.py
def parameters_Inputs(request, jobid):
if request.method == "POST":
form1 = ActionInputsForm(request.POST, request.FILES, jobid)
if form1.is_bound:
form1.save()
return render(request, 'goodlog.html', {'jobid': jobid})
elif request.method == "GET":
form1 = ActionInputsForm(jobid)
return render(request, 'inputsform.html',
{'form1': form1, 'jobid': jobid})
Now the request.POST['userproject'] is empty, which means the jobid has not been modified by init, the request.FILES looks correct but the validation is false. It says Unicode object has no attrite get, which is related to the uploaded file. Any idea about what is wrong? Thanks very much.
The following works:(thanks to Vladimir Danilov)
def __init__(self, jobid, *args, **kwargs):
super(ActionInputsForm, self).__init__(*args, **kwargs)
self.fields['userproject'].initial = UserProject.objects.get(pid=jobid)
def clean_userproject(self):
userproject = self.cleaned_data['userproject']
if not userproject:
raise forms.ValidationError("---UserProject not found.")
return userproject
def parameters_Inputs(request, jobid):
if request.method == "POST":
form1 = ActionInputsForm(jobid, request.POST, request.FILES)
.......
Not answer, but do you mean ActionInputsForm instead of Action_Inputs in these lines?
form1 = Action_Inputs(request.POST, request.FILES, jobid)
# ...
form1 = Action_inputs(jobid)
Also, you should write ActionInputsForm(jobid, request.POST, request.FILES).
Because in your case jobid will be request.POST.

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.