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.
Related
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"),
)
I have two models User and Group.
I'm implementing an action "Change Groups" in UsersAdmin that redirects to an intermediate page with 2 MultipleChoiceFields for Groups, that I want to be used to either remove users from certain groups, add users to other groups, or do both in one go (i.e. move them).
The docs are very short about this subject, so in order to do this, I'm following this article.
Here's my form:
class ChangeUsersGroupsForm(forms.Form):
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
from_groups = forms.ModelMultipleChoiceField(Group.objects, required=False)
to_groups = forms.ModelMultipleChoiceField(Group.objects, required=False)
My admin action:
def change_groups_action(self, request, queryset):
if 'apply' in request.POST:
from_groups = request.POST["from_groups"]
to_groups = request.POST["to_groups"]
from_groups_qs = Group.objects.filter(pk__in=from_groups).all()
to_groups_qs = Group.objects.filter(pk__in=to_groups).all()
user_ids = [u.user_id for u in queryset]
# task that will do the job of actually moving the users
change_users_groups.delay(from_groups_qs, to_groups_qs)
self.message_user(request, "Changed groups of %s users" % len(user_ids))
return HttpResponseRedirect(request.get_full_path())
form = ChangeUsersGroupsForm(initial={'_selected_action': queryset.values_list('id', flat=True)})
return render(request, "admin/change_users_groups.html", {'queryset': queryset, 'form': form})
change_groups_action.short_description = "Change Groups"
Here's my template:
<!-- users/templates/admin/change_users_groups.html -->
{% extends "admin/base_site.html" %} {% block content %}
<form action="" method="post">
{% csrf_token %}
{{ form }}
<br />
<br />
<p>The Group changes will be applied to the following users:</p>
<ul>
{{ queryset|unordered_list }}
</ul>
<input type="hidden" name="action" value="change_groups_action" />
<input type="submit" name="apply" value="Confirm" />
</form>
{% endblock %}
This is how the intermediate page renders:
First (but minor) issue is that the form fields are displayed in a row, instead of each in one row. But let's skip that for now.
The big issue is that when I select a Group, nothing happens, the Group doesn't seem to be selected.
Instead I see the following error on the browser Console:
Uncaught TypeError: django.jQuery is not a function
This error is printed every time I click on an option.
Anyone knows what's going on?
Django 2.2
Python 3.8.10
I've had this problem before, but in different situation (unrelated to Django admin). I almost always turned out to be because JQuery was not loaded or it was loaded too late in the template.
According to Django documentation:
If you want to use jQuery in your own admin JavaScript without
including a second copy, you can use the django.jQuery object on
changelist and add/edit views. Also, your own admin forms or widgets
depending on django.jQuery must specify js=['admin/js/jquery.init.js',
…] when declaring form media assets.
So that would make your form class look like:
class ChangeUsersGroupsForm(forms.Form):
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
from_groups = forms.ModelMultipleChoiceField(Group.objects, required=False)
to_groups = forms.ModelMultipleChoiceField(Group.objects, required=False)
class Media:
js = ['admin/js/jquery.init.js']
Regarding your form field form. I suggest rendering each field separately like so:
{{ form.from_groups }}
<br/>
{{ form.to_groups }}
This seems like the simplest solution
Let me know if that helps :)
I'm trying to dynamically set the value of a radio field within the HTML template but I'm not sure how to set the default selection. I want to do this because the forms I want to use are forms that can be saved and re-edited if need be, so I want the default values to be values set when previously saved/submitted.
the wtform fields
field_1 = StringField('field_1')
radio_1 = RadioField('radio_1', choices=[(1,'Yes'),(2,'No')])
what i want to be able to do is something like this:
data is database data
{% if data.field_1 = 'X' %}
{{ form.radio_1(id="radio_1",class="ff-style-radio",default=1) }}
{% else %}
{{ form.radio_1(id="radio_1",class="ff-style-radio",default=2) }}
{% endif %}
I haven't had any success trying this method, swapping default for value, etc. Is something like this possible? If not how would I separate the wtform radio field choices so I can just manually mark which selection is checked? Or should I just use the base HTML method and do something like this:
<ul class="ff-style-radio" id="radio_1">
<li>
{% if data.field_1 = 'X' %}
<input id="radio_1-0" name="radio_1" type="radio" value="Yes" checked>
{% else %}
<input id="radio_1-0" name="radio_1" type="radio" value="Yes">
{% endif %}
<label for="radio_1-0">Yes</label>
</li>
<li>
{% if data.field_1 = 'X' %}
<input id="radio_1-1" name="radio_1" type="radio" value="No">
{% else %}
<input id="radio_1-1" name="radio_1" type="radio" value="No" checked>
{% endif %}
<label for="radio_1-1">No</label>
</li>
</ul>
Thanks for any help
If you want to use the form to edit data already in your database, you should supply this data to the form at creation time in your view function. This will allow you to keep your templates simple and reusable. Data from the database can be passed to the form either as an object or as a dictionary https://wtforms.readthedocs.io/en/stable/forms.html. If your form maps directly to a database table you can just pass this unmodified from your query. In the following code I've followed your example and created a new dictionary to set the value of radio_1 based on the value of field_1.
#app.route("/myurl")
def myview():
# do database lookup here
olddata = {"radio_1": 1 if data.field_1 == "X" else 2}
form = Myform(formdata=request.form, data=olddata)
if request.method == "POST" and form.validate():
# .....
In HTML
<form method='post'>
{% csrf_token %}
{{form.name}}
{{form.email}}
<textarea name='message'>{{form.message}}</textarea>
<button type='submit'>Send</button>
</form>
How I can get the message data from my textarea in my view? Or the right way is put {{form.as_p}} in form? Please help
Above answer is perfectly alright if you want this on view. Another safe method is request.POST.get('message') it will return None instead of error message, if it's available.
But, you want it on template then you can use
{{ form.data.message }}
request.POST is basically a dictionary returned. It contains csrfmiddlewaretoken and all form data with name specified as key in the request.POST dict.
So, as per your form, you can get the message data from textarea by simply writing
message_data = request.POST['message']
in view.py .
If you want to display form in your style then do it manually. Otherwise, django provides few techniques to render form, and they are as follows:
{{ form.as_table }} will render form as table cells wrapped in <tr> tags,
{{ form.as_p }} will render form wrapped in <p> tags,
{{ form.as_ul }} will render form wrapped in <li> tags.
Now, it depends upon you, how you want your form to look on page.
I have spent some time on this but cannot figure out the exact cause of the following behaviour.
I have a Django form and in the template I am trying to see if an integer is present in a list and then doing something with it.
{% if pk in form.area.value %} {# form.area.value is a list like [7,21] #}
do something
{% endif%}
Everything works fine except in cases where the form is reloaded after a validation error. In such cases, the list that I am comparing with, gets converted to list of strings (from a list of ints) on its own and the if test fails. So [7,21] above becomes ['7','21']
In case it helps, this is how the form is being rendered in the view:
On the GET request (where the if condition works fine):
form = SchoolForm(instance = school)
return render(request, 'edit-school.html', {'form': form, 'school_id': school_id})
After the POST request (where the if condition fails in the template):
form = SchoolForm(request.POST or None, instance=school, request=request)
return render(request, 'edit-school.html', {'form': form, 'school_id': school_id})
Update: Got it to work by converting string values after the POST request in the form back to int by declaring another method in the form (as suggested by #bruno in the answer below) and using it in the template. Here's the code snippet (of how its being used in the template):
<div class="checkbox">
{% for pk, choice in form.area.field.widget.choices %}
<label for="id_{{sub_type}}_{{form.area.name}}_{{ forloop.counter0 }}">
<input class="form-control" id="id_{{sub_type}}_{{form.area.name}}_{{ forloop.counter0 }}" name="{{form.area.name}}" type="checkbox" value="{{ pk }}" {% if pk in form.area_values %} checked="checked" {% endif %}/>
<span class="badge">{{ choice }}</span>
</label>
{% endfor %}
</div>
After the user submitted the form, {{ form.area.value }} (which in Python resolves to form["area"].value) is populated with the raw data from request.POST, which are indeed strings.
You didn't clearly explain what kind of "fancy thing" you're trying to do in the template so it's hard to come with a "best" solution for your use case, but as a general rule, templates are not the place for anything fancy - that's what Python is for (either in your view, in your form, in a custom templatetag or filter etc).
A very quick and simple solution would be to add a method to your form that returns the area boundfield values as ints:
class SchoolForm(forms.ModelForm):
# your code here
def area_values(self):
# XXX this might require some conditionals or
# error handling to be failsafe
# works with both python 2.x and 3.x
return [int(v) for v in self["area"].value()]
then in your template, replace {% if pk in form.area.value %} with {% if pk in form.area_values %}