Django passing parameter to CreateView and filling the form with it - python

I want to make a CreateView with the form already completed with an argument that will be passed to it from another view.
There is the initial view with an input field. Like this:
<form role="form" action="" method="post">
{% csrf_token %}
<div class="col-sm-6">
<div class="form-group">
<div class="form-line">
<label>{{form.cpf.label}}</label><strong style="color:red;"> *</strong>
{% if form.cpf.errors %}<label class="error">{% for error in form.cpf.errors %}{{error}}{% endfor %}</label>{% endif %}
{{form.cpf}}
</div>
</div>
</div>
<button class="btn btn-success foot-btn" type="submit"><i style="vertical-align:middle" class="material-icons">add</i><span style="vertical-align:middle">Verificar</span></button>
</form>
When the user submits this form he will be redirected to another page. Asking if he wants to insert that value in the DB.
Right now I'm trying to redirect like this, trying to pass the POST value as a parameter to the CreateView
return redirect(reverse('blacklist:addcpfview', args=(request.POST['cpf'],)))
But I can't seem to get this parameter in the CreateView.
I know this I'm probably very wrong in the way I'm currently doing, but I'm a beginner with Django and want to know the best way to do this.
UPDATE
The CreateView
class AdicionarCPFView(CreateView):
form_class = CPFForm
template_name = 'blacklist/cpf/addCPF.html'
success_url = reverse_lazy('blacklist:cpfselectview')
def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs)
context['title_complete'] = 'Adicionar CPF'
return context

As you use CreateView - it has a special method to fill initial values - get_initial
What you need is to override this method and read variable from kwargs there.
def get_initial(self):
initial = super().get_initial()
# cpf - it's the name of the field on your current form
# self.args will be filled from URL. I'd suggest to use named parameters
# so you can access e.g. self.kwargs['cpf_initial']
initial['cpf'] = self.args[0]
return initial
Another way to achieve this is to use session.
So when before you redirect you do request.session['initial_cpf'] = request.POST['cpf]
And in create view you access not self.args but self.request.session
And a side note - in first view it's a good practice not to read variable from POST, but use a form instead.

Related

Is it possible to add an input field to Wagtails custom bulk actions?

Is it possible to add an input field to Wagtails custom bulk actions?
In the template from the documentation example there is a block called form_section. Here I want to add a separate form to add another input field. Another position would be possible as well, of course.
<!-- /path/to/confirm_bulk_import.html -->
# ...
{% block form_section %}
{% if images %}
{% trans 'Yes, import' as action_button_text %}
{% trans "No, don't import" as no_action_button_text %}
# Can I use my own confirmation form here? How about its view?:
{% include 'wagtailadmin/bulk_actions/confirmation/form.html' with action_button_class="serious" %}
{% else %}
{% include 'wagtailadmin/bulk_actions/confirmation/go_back.html' %}
{% endif %}
{% endblock form_section %}
I would love to bulk select Image instances to add them to a Page. So I need to have a ChoiceField to select the Page. This would also require a customized View for the logic behind this "import". The latter is not the question. I am just wondering how I can add this input field and alter the view of a these marvelous bulk actions.
Standard bulk actions for images in Wagtail also include "Add images to collection":
The following is how the second step of this action looks like. I would love to add a custom bulk action in this sense to add images to a page (via a ImagePageRelation / InlinePanel)
Wagtail admin portal is using pure HTML and CSS. So everything coming to the python side is received via a HTML form. That means every button click in UI should associate with a HTML form and from wagtail side you can find it in the request.
Execute Action Method
If you went through the bulk action documentation, you will find that after the form is submitted, execute_action class method will be executed. Now you need to understand the parameters of this method.
#classmethod
def execute_action(cls, objects, **kwargs):
raise NotImplementedError("execute_action needs to be implemented")
As this is a class method, the first parameter is the class type which this method is on. You can learn more about class methods in the python documentation.
The 2nd parameter objects is the list of objects that you have selected for this bulk operation. To be precise, this is the list of objects that you have selected with the correct permission level. In the default implementation, permission is given for all the objects. But you can override this behavior.
def check_perm(self, obj):
return True
You can override this method in your custom bulk action class and check permission for each object. As the objects parameter, you will receive the only objects which have check_perm(obj)==True, from the list of objects you selected.
The 3rd parameter of execute_action class method is a keyworded argument list (a dictionary to be precise). This dictionary is obtained by calling the following method.
def get_execution_context(self):
return {}
Default behavior of this method is to return empty dictionary. But you can override this to send anything. Because execute_action is a class method, it can't access the instant variables. So this method is very helpful to pass instance variables to execute_action class method.
Lets look at an example.
#hooks.register('register_bulk_action')
class CustomBulkAction(ImageBulkAction):
display_name = _("A Thing")
aria_label = _("A thing to do")
action_type = "thing"
template_name = "appname/bulk/something.html"
def get_execution_context(self):
print(self.request)
return super().get_execution_context()
If you run this example, you can see the data submitted from the HTML form.
<WSGIRequest: POST '/admin/bulk/image/customimage/thing/?next=%2Fadmin%2Fimages%2F&id=1'>
Override the HTML Form
In the bulk action template, you can't find any HTML <form></form> tag. It is because the form with action buttons are in wagtailadmin/bulk_actions/confirmation/form.html file that you have import in the template. You can create the copy of that file and change it's behavior.
<form action="{{ submit_url }}" method="POST">
{% include 'wagtailadmin/shared/non_field_errors.html' %}
{% csrf_token %}
{% block form_fields %}
<!-- Custom Fields goes here -->
{% endblock form_fields %}
<input type="submit" value="{{ action_button_text }}" class="button {{ action_button_class }}" />
{{ no_action_button_text }}
</form>
You can add custom fields you need in the area that I mentioned above sample code and values of those additional fields will be there in self.request.POST parameter. This is the easiest way to get something from the template to python side.
Django Forms
But that is not the best way. Django recommends using forms for these purposes. You can find more about Django forms in the documentation.
Almost every place that there is a form in a wagtail template, there is a associated Django form. In this case, the instance variable form_class is used to associate a bulk action template with a Django form.
class MyForm(forms.Form):
extra_field = forms.CharField(
max_length=100,
required=True,
)
#hooks.register('register_bulk_action')
class CustomBulkAction(ImageBulkAction):
display_name = _("A Thing")
aria_label = _("A thing to do")
action_type = "thing"
template_name = "appname/bulk/something.html"
form_class = MyForm
def get_execution_context(self):
print(self.cleaned_form.data)
return super().get_execution_context()
And very simply, I will add all the form fields to the template as in the below sample code.
<form action="{{ submit_url }}" method="POST">
{% include 'wagtailadmin/shared/non_field_errors.html' %}
{% csrf_token %}
{% block form_fields %}
{% for field in form %}
<div class="fieldWrapper">
{{ field.label_tag }} {{ field }}
{{ field.errors }}
</div>
{% endfor %}
{% endblock form_fields %}
<input type="submit" value="{{ action_button_text }}" class="button {{ action_button_class }}" />
{{ no_action_button_text }}
</form>
Now this will print the data received from the HTML form. What we need to do is to pass the form data as kwargs to the execute_action class method.
Final Example
#hooks.register('register_bulk_action')
class CustomBulkAction(ImageBulkAction):
display_name = _("A Thing")
aria_label = _("A thing to do")
action_type = "thing"
template_name = "appname/bulk/something.html"
form_class = MyForm
def get_execution_context(self):
data = super().get_execution_context()
data['form'] = self.cleaned_form
return data
#classmethod
def execute_action(cls, objects, **kwargs):
print("KWARGS:", kwargs)
print(kwargs['form'].cleaned_data['extra_field'])
# Do what you want
return 0, 0
I believe this was helpful and answered all the questions related to bulk action submission.
With forms.ModelChoiceField in your form, you can get values from Django Models and pass them to the HTML field. You have to pass a queryset in the constructor.
extra_field = forms.ModelChoiceField(
required=True,
queryset=Collection.objects.order_by("name"),
)

Stuck with django form validation

I'm trying to get validation running on a django form used to retrieve a list of objects in a ListView View. Despite having read django docs and many other questions here, I can't find out what's wrong in this simple test code:
form.html
<form action="list.html" method="get">
{{ form }}
<input type="submit" value="Submit">
</form>
list.html
<ul>
{% for area in object_list %}
<li>{{ area.name }}</li>
{% endfor %}
</ul>
forms.py
from django import forms
class SearchArea(forms.Form):
area = forms.CharField(label='Area code', max_length=6)
def clean_area(self):
area = self.cleaned_data['area'].upper()
if '2' in area:
raise forms.ValidationError("Error!")
return area
views.py
class HomePageView(FormView):
template_name = 'form.html'
form_class = SearchArea
class AreaListView(ListView):
template_name = 'list.html'
model = AreaCentral
def get_queryset(self):
q = self.request.GET.get('area')
return AreaCentral.objects.filter(area__istartswith=q)
When I try to submit something like "2e" I would expect a validation error, instead the form is submitted. Moreover I can see in the GET parameters that 'area' is not even converted to uppercase ('2E' instead of '2e').
The default a FormView will only process the form on POST; the GET is for initially displaying the empty form. So you need to use method="post" in your template form element.
Your action attribute is also suspect; it needs to point to the URL of the form view. If that actually is the URL, note it's not usual to use extensions like ".html" in Django URLs, and I would recommend not doing so.

Getting a form in Django DetailView

I am currently trying to show a custom form where I alter the form in the corresponding view
in the .html
<form action="{% url 'systems_system_update' system.id %}" id="system_update_form" method="post" class="form">
{% csrf_token %}
{% buttons %}
<button type="submit" class="btn btn-primary">Update {{ system.name }}</button>
{% endbuttons %}
{% bootstrap_form system_update_form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Update {{ system.name }}</button>
{% endbuttons %}
</form>
So I'm trying to show system_update_form where it is defined in the view
in SystemDetailView
class SystemDetailView(DetailView):
"""Detail view for Systems"""
form_class = SystemForm
model = System
template_name = 'systems/system_detail.html'
def get_form(self, form_class):
form = super(SystemDetailView, self).get_form(form_class)
form.fields['primary_purpose_business_use'].label = "Primary purpose/business use"
form.fields['secondary_purpose_business_uses'].label = "Secondary purpose/business uses"
return form
def get_context_data(self, **kwargs):
context = super(SystemDetailView, self).get_context_data(**kwargs)
context.update({
'system_update_form': self.form_class(instance=self.get_object()),
'object_name': self.object.name,
'user_can_edit': self.request.user.has_perm(
'services.change_system'),
'user_can_delete': self.request.user.has_perm(
'services.delete_system'),
'object_events': self.object.events.all(),
})
return context
So, I'm updating the context and setting 'system_update_form' to the form and I'm trying to update the form by using get_form, but I don't think DetailView has the get_form method for overriding.
Updating it in the forms is not an option because SystemForm is used in many different places and needs to be altered for this view specifically
DetailView does not have a get_form() method as it does not uses a form, thereby your get_form() method is not being called.
Instead of that, you can manually instantiate the form in your get_form() method and call this method when generating the context.
Also, in your code, you are passing instance to the form by calling self.get_object(). This will lead to another query for getting the object as Django has already fetched the object before. Instead of doing that, you can directly pass object using self.object.
class SystemDetailView(DetailView):
def get_form(self):
form = self.form_class(instance=self.object) # instantiate the form
# modify the form fields
form.fields['primary_purpose_business_use'].label = "Primary purpose/business use"
form.fields['secondary_purpose_business_uses'].label = "Secondary purpose/business uses"
return form
def get_context_data(self, **kwargs):
context = super(SystemDetailView, self).get_context_data(**kwargs)
context.update({
'system_update_form': self.get_form(), # get the form instance
'object_name': self.object.name,
'user_can_edit': self.request.user.has_perm(
'services.change_system'),
'user_can_delete': self.request.user.has_perm(
'services.delete_system'),
'object_events': self.object.events.all(),
})
return context

Editing django forms using django bootstrap forms

I am working with django forms and am using django bootstrap form(https://django-bootstrap-form.readthedocs.org/en/latest/) for UI. I am able to create forms in html using the django bootstrap form. Now the problem is that i want to edit the forms and update the record in the database.
My question is how can i use the django bootstrap form to provide a form for editing
for eg:
i am using
<form role="form" action="/abc/" method="POST">{% csrf_token %}
<div class="form-group">
{{ form.name.errors }}
{{ form.name|bootstrap }}
</div>
</form>
this is the form when filling for the first time. When i click on the edit option i want the same UI but with value="the_value_saved_in_the_database" something like
{{ form.name|bootstrap value="_data_"}}
How can i achieve it?
Hope you understand the problem.
Thanks in advance
You need to load the form with data (called binding the form) before you render it. If the form represents some data that you have stored in the model, then create a ModelForm and pass in the model instance for which you want to edit the data.
Here is an example:
class AddressBook(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
class AddressForm(forms.ModelForm):
class Meta:
model = AddressBook
def edit_address(request, pk=None):
existing_entry = AddressBook.objects.get(pk=pk)
form = AddressForm(instance=existing_entry)
return render(request, 'edit.html', {'form': form})
In urls.py:
url('^address/edit/(?P<pk>\d+)$', 'edit_address', name="edit"),
url('^address/save/$', 'save_address', name="save"),
Now, when you call your view http://localhost:8000/address/edit/1, the form will be populated by the data for the entry whose primary key is 1 ready for editing. In your template, simply render the form, and point it to the save view:
<form method="post" action="{% url 'save' %}">
{% csrf_token %}
{{ form|bootstrap }}
</form>
If you are going to be doing this often, its easier to use the generic class based views (like CreateView, EditView) to simplify your code.

Web server ini-file editor using django

I wish to edit ini files over web server, decided to use django, been using it for few days now. I can't figure out how to accomplish this. I have ini file structure looking like this:
{'GROUP', {PROPERTY : VALUE}}
Example when I read this kind of ini file:
[LOG]
FilePath = C:/Log
[CMD]
Level = 5
I will get my data structure filled like this:
{'LOG', {'FilePath' : 'C:/Log',},
{'CMD', {'Level', '5'}}}
Loop looks like this:
for group in settingsDict:
print group # group
for property in settingsDict[group]:
print property , # property
print settingsDict[group][property] # value
I am using ini file parser.
I am having trouble understanding how to correctly develop in django: views.py is some kind of controller for django and templates are views and model would be my ini file (probably linked with db using django model), or am I getting something wrong?
I have no problem passing this dictionary to template, making a for loop in it and creating html tags like: <input type="text" name={{ property }} value={{ value }} maxlength="100" />. But how do I then post all the edited values back to control to save them in file (or db)? I Would need all 3 values, that is GROUP, PROPERTY and VALUE.
Then I discovered django also has html widgets, which you create in views.py and then pass it to template. But this is where I stop understanding things, since I am creating widget in my controller class, but even if I am.
Shall I create a list of all django widgets and pass it to template? Same question occurs, how do I get all the widget values back to controller (views.py)?
Update (11.6.2012):
My code looks like this:
views.py
class DynForm(forms.Form):
def setFields(self, kwds):
keys = kwds.keys()
keys.sort()
for k in keys:
self.fields[k] = kwds[k]
def settings(request):
global Settings #my ini dict
kwargs = {}
for group in Settings:
for property in Settings[group]:
kwargs[property] = forms.CharField(label = property, initial = Settings[group][property])
f = DynForm()
f.setFields(kwargs)
return render_to_response('/settings.html',
{
'textWidget' : f,
})
#csrf_exempt
def save(request):
if request.method == 'POST': # If the form has been submitted...
form = DynForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# process form data
# and return response
settings.html
<form action="/save/" method="post">
{% csrf_token %}
{% for field in textWidget %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Save" /></p>
</form>
The problem is, DynForm(request.POST) returns null so I can't get field values. My request.POST is correct, containing all fields and values. As much as I know, I am not suppose to parse request.POST data "by hands"?
OK, finally figured it out, taking me a lot of time (I am lacking a lot of python and django knowledge). I can't paste final solution because of copy right permissions, here is the concept:
Form
class DynamicForm(forms.Form):
def __init__(self,*k,**kw):
forms.Form.__init__(self,*k,**kw)
# loop over data from **kw
# create field
# set field default value
Notes about this code:
If form doesn't use super(SuperForm, self).__init__(*args, **kwargs), you must use forms.Form.__init__(self,*k,**kw) so you can append fields to form using self.fields attribute.
If you need to use default field value, use self.data[field] = defVal not initial = defVal. Form becomes unbound and you won't be able to parse data in your request.POST method. Unbound form (and with errors) will always return is_valid() False.
With this class, you have no problems parsing request.POST data. Looping over dynamic form fields looks like this:
View
for name,field in form.fields.items():
# name - field name
# form.data[name] - field value
Notes:
For the sake of simplisity use #csrf_exempt tag before POST method. See http://jordanmessina.com/2010/05/24/django-1-2-csrf-verification-failed/
Template code loops over fields in form displaying field label and value separated with :
Template
<form action="/Tris/save/" method="post">
{% csrf_token %}
{% for field in textWidget %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.non_field_errors }}
{{ field.label }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Save" /></p>
</form>
Most of the solution is from here: http://jacobian.org/writing/dynamic-form-generation/ and django documentation.

Categories

Resources