currently have a model that has a model.FileField() attribute, and when rendering in my django template I just iterate over the fields, such as
{% for field in form.visible_fields %}
<div class="form-group">
{{field.errors}}
<label for="{{field.auto_id}}">{{field.label}}</label>
{{field}}
{% endfor %}
However, when the template renders the ClearableFileInput widget, I want to add some space between the href and the checkbox for clearing the widget. Any ideas on how to access those specific "parts" of the field?
You have to override the default ClearableFileInput and set those rendering attributes to your taste
class MyClearableInput(ClearableFileInput):
template_with_initial = '%(initial_text)s: %(initial)s %(clear_template)s<br />%(input_text)s: %(input)s'
template_with_clear = '%(clear)s <label for="%(clear_checkbox_id)s">%(clear_checkbox_label)s</label>'
url_markup_template = '{1}'
I've put the initial attributes, but you have to change them to reflect your desired output. It's pretty self-explanatory. Then in your form, override the widgets to use this class using the Meta/widgets attribute.
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"),
)
please help to solve the problem.
I made a form for uploading photos.
models:
class UserProfile(User):
drum_photo = models.ImageField(
'Фото инструмента',
upload_to='userprofile/drum_photo/',
blank=True,
null=True,
)
forms:
class DrumDataForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = (
'drum_photo',
)
template.html:
<div class="cell image_outer">
<label class="label">{{ form.drum_photo.label }}</label>
{{ form.drum_photo }}
{% if drum_photo %}
{% thumbnail drum_photo "230x230" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
{% endthumbnail %}
{% endif %}
{{ form.drum_photo.errors }}
</div>
resulting browser displays the following markup:
<div class="cell image_outer">
<label class="label">foto</label>
now: userprofile/drum_photo/kYNP5kPnTOU_2.jpg <input id="drum_photo-clear_id" name="drum_photo-clear" type="checkbox"> <label for="drum_photo-clear_id">Clear</label><br>Изменить: <input id="id_drum_photo" name="drum_photo" type="file">
<img src="/media/cache/0a/ca/0acac93baa399abad7d3c048ff20d5db.jpg" width="230" height="230">
</div>
the problem is that I need to change the layout (add or remove some of the elements). please tell me where you store templates that need to change?
I think what you are asking is where is the template that renders a form field. This is controlled by the widget which is used to render the field.
A django form field contains two things:
Widget
Validation
The HTML and rendering is the responsibility of the widget. Django comes with a collection of widgets that are used in forms. Most of these correspond to the normal form elements, but there are some specialized ones.
To customize what HTML a form field will output, you need to customize the widget that is used to render the field.
You file upload form uses a FileField which uses by default the ClearableFileInput widget.
Each widget takes an optional attrs argument, which you can use to control the HTML attributes for the element that is rendered. For example, if I want to change the width and height:
class DrumDataForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DrumDataForm, self).__init__(*args, **kwargs)
self.fields['drum_photo'] = forms.FileUploadField(
widget = forms.ClearableFileInput(
attrs={'width': 200,
'height': 300}))
class Meta:
model = UserProfile
If you want more styling flexibility on your form fields see the new form assets section, which allows you to customize the CSS that is used to style forms.
For even more customization you can create your own widget by subclassing the base widget classes; or by using a third party library like django-crispy-forms.
I'm trying to set maxlength html attribute in django template but when I try to get the value I specified in my model by using {{field.max_length}} nothing is displayed. I can get any other field value just fine.
Relevant Snippet:
{% for field in form %}
{% if "_color" in field.name %}
<div class="control-group">
<label class="control-label">{{field.label}}</label>
<input type="text" ng-init="{{field.name}}='{{field.value}}'" ng-model="{{field.name}}" ng-change="{{field.name}}_picker={{field.name}}" placeholder="{{field.label}}" maxlength="{{field.max_length}}">
The field you get by iterating trough your form is actually a forms.BoundField, and does not have all the attributes of your Field. You can access the Field instance through the field attribute of your BoundField. So, {{ field.field.max_length }} should work.
I'm looking at a package called crispy forms I was wondering if it was possible to change the html name from within the template?
i.e.
{{ form.username|changename }}
Rules:
I don't want to change the original form or view (at all), I only have access to the template.
Since you're changing the label, I suggest you do it template-ish, without using a templatetag or a filter:
<label for="id_username">Your label name here:</label>
{{ form.username }}
Or, if you can't resist, you have to apply a filter to your field.label property:
{{ form.username.label|my_custom_filter }}
A simple filter could be:
#register.filter(name='my_custom_filter')
def my_custom_filter(value):
# here value is the value of your label
# process it as you please then return the new value
if value == 'changeme':
return 'changed label'
return value
This is just an example to show you how a template filter works, you have then to elaborate it and change name, logic etc etc
This could be over simplifying it, but what not just raw output it?
<label for="">what ever you want</label>
<input type="text" name="{{ form.username.html_name }}" id="{{ form.username.auto_id }}">
I would still do this in the form.py myself but if all you have is the template to work with then it does not need anything fancy here.
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.