Is it possible to determine if a template variable in a Django template satisfies a regular expression? In the following template, I want to set the CSS class for the paragraph tag that contains the help text based on whether or not the help text for that field satisfies a regular expression. Here is the template with some pseudocode thrown in:
{% for field in form.visible_fields %}
<div class="form-group">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
{% if field.help_text|lower (BEGINS WITH SOME STRING) %} # Pseudocode
<p class="select-help-text">{{ field.help_text|safe }}</p>
{% else %}
<p class="help-text">{{ field.help_text|safe }}</p>
{% endif %}
{% endif %}
</div>
{% endfor %}
For example, if the help_text as defined in the associated form starts with the text string "Hold down Ctrl", then the CSS class should be set to select-help-text, otherwise it should just be set to help-text.
I understand that Django regular expressions are based on Python regexes, but Python regex evaluations always seems to be done using the re module which isn't accessible in a Django template. I also looked through the Django documentation but couldn't find a way to do this.
UPDATE
I still can't get this code to work.
Melvyn, who answered below, is technically correct. You should avoid putting conditional logic in Django templates. To that end, I changed my template per the Django documentation:
{% for field in form.visible_fields %}
<div class="form-group">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help-text">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
I then added an __init__ method to my forms ModelForm class that looks at the label for the form fields that shouldn't have their help text displayed and sets help_text to a "falsey" value so the condition in the template will fail:
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
for visible in self.visible_fields():
if visible.field.label == 'foo' or visible.field.label == 'bar' or visible.field.label == 'baz':
visible.field.help_text = False
However, I'm still seeing the help text whether I set help_text to False or None or an empty string. Is this some type of timing issue or have I made a mistake that I'm just not seeing?
DTL (Django Template Language) is not meant to program. In fact, I wouldn't even write a template tag for this. In your view (or form, or field or widget), you have all the power to change the help text to a 2-tuple or dict with a label, so why don't ya ;)
Perhaps the best approach is in the field or widget and just add the desired class to the widget based on the help text.
Overriding model form fields
So, to provide the answer for the update, this is the trimmed down example I used:
models.py
from django.db import models
class Sensor(models.Model):
color = models.CharField(
max_length=20, verbose_name="visible", help_text="Colors are visible"
)
sound = models.CharField(
max_length=20, verbose_name="audible", help_text="Sounds are audible"
)
forms.py
class BasicModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for visible in self.visible_fields():
normalized = visible.label.lower()
if "audible" == normalized:
visible.help_text = ""
class Meta:
model = Sensor
fields = "__all__"
tests.py
from django.template import Template, Context
class FormTest(TestCase):
def test_modelform(self):
form = BasicModelForm()
self.assertEqual(form["sound"].help_text, "")
def test_template(self):
form = BasicModelForm()
context = Context({"form": form})
template = Template("{{ form.sound.help_text }} | {{form.color.help_text}}")
actual = template.render(context)
self.assertEqual(""" | Colors are visible""", actual)
It failed before normalizing the label value. I used the test to have PyCharm break at the assignment, but it never got to the assignment, so then I braked at the if statement to see "Audible" instead of "audible".
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'm trying to show a model's ForeignKey value in a template, all other fields are showing fine but I couldn't make it work. Here is my code:
models.py:
class BodyPart(models.Model):
body_part = models.CharField(max_length=20, unique = True)
class Exercise(models.Model):
body_part = models.ForeignKey(BodyPart, on_delete=models.CASCADE, default = "bodypart", related_name="bodypart")
views.py:
exercises = Exercise.objects.filter(category=exercisedetailcategory).values()
context = {
"exercises" : exercises,
}
return render(request,"exercises-categories.html",context)
template:
{% for exercise in exercises %}
<span class="post-meta-category">{{exercise.body_part}}</span>
<div class="post-item-description">
{{exercise.title}}
<p>{{exercise.content}}</p>
{% endfor %}
This is one of the many reasons why you should not use .values(). If you pass Exercise models, you can fetch the related object into memory. You can make use of .select_related(..) to optimize the query:
exercises = Exercise.objects.filter(
category=exercisedetailcategory
).select_related('body_part')
context = {
'exercises' : exercises,
}
return render(request, 'exercises-categories.html', context)
Then in the template, we can render this with:
{% for exercise in exercises %}
<span class="post-meta-category">{{ exercise.body_part.body_part }}</span>
<div class="post-item-description">
{{ exercise.title }}
<p>{{ exercise.content }}</p>
{% endfor %}
You can furthermore implement a __str__ method for BodyPart:
class BodyPart(models.Model):
body_part = models.CharField(max_length=20, unique=True)
def __str__(self):
return self.body_part
and then render this with:
{% for exercise in exercises %}
<span class="post-meta-category">{{ exercise.body_part }}</span>
<div class="post-item-description">
{{ exercise.title }}
<p>{{ exercise.content }}</p>
{% endfor %}
in your exercise model, ignore the default part.(its possible to show any message that tell users "no body_part" such as
{% if not exercise.body_part %} -> No thing to show)
and make sure, you have a value in your exercise.body_part Which it means you have to have an object in your BodyPart model in relation to the current object of this model.
also it should be {{ exercise.body_part.body_part }}
the second one is to extract the value of related BodyPart objects value
I have a forms.ModelForm 'CreateUserForm'.
I want to set a property for each form field to be later used in the template.
In this case, I want to set a icon name to specify which icon name should be used for each field.
class CreateUserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput)
icon_names = ['person', 'email', 'enhanced_encryption']
class Meta:
model = User
fields = ['username', 'email', 'password']
I've had trouble iterating over both the field AND the field's property 'icon_names'. I can't really zip() without losing functionality.
Currently I've hacked together iteration by using the 'forloop.parentloop.counter'
{% for field in form %}
<div class="form-group">
<div class="input-field">
<i class="icons">
{% for icon in form.icon_names %}
{% if forloop.parentloop.counter == forloop.counter %}
{{ icon }}
{% endif %}
{% endfor %}
</i>
<input type="text" id="autocomplete-input" class="autocomplete">
<label class="control-label" for="autocomplete-input">{{ field.label_tag }}</label>
</div>
</div>
{% endfor %}
Which produces the intended result, but it seems redundant, especially if I wanted to add another field property in the future.
What's the proper way to do this?
One idea would be to pass the zipped list in the context, such as:
context = {'fields_with_icons': zip(form.icon_names, [field for field in form])}
and then
{% for field, icon in fields %}
{{ field }}
{{ icon }}
{% endfor %}
There are two ways i could do this both involving adding an extra html attribute on the fields widget
see age field below, i would use the self.fields to get the field widget and add the extra icon attribute on its attrs dictionary...for this to work you should ensure it comes after the call to super().__init__(*args, **kwargs) others the self.fields will not have been populated....i'd use this when i dont have anything else i need to adjust on the widget class.
https://docs.djangoproject.com/en/2.0/ref/forms/widgets/#styling-widget-instances
see name field below, You could do this on the Meta class https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/#overriding-the-default-fields
The form
class PersonForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['age'].widget.attrs['icon'] = 'age'
class Meta:
model = models.Person
fields = ('name', 'age')
widgets = {
'name': forms.TextInput(attrs={'icon': 'email'})
}
And on the template when looping over the fields id get it like this
{% for field in form %}
{{ field.field.widget.attrs.icon }}
{% endfor %}
My form model is defined like this:
from com.example.cms.forms import FieldList as MyAppFieldList
from flask_wtf import Form
from wtforms import fields
from wtforms import validators
class FacebookPostTranslationForm(Form):
language = fields.StringField('Language', [validators.InputRequired(), validators.Length(min=2, max=2)])
title = fields.TextAreaField('Title', [validators.InputRequired()])
description = fields.TextAreaField('Description', [validators.InputRequired()])
linkUrl = fields.StringField('Link', [validators.InputRequired(), validators.URL()])
pictureUrl = fields.StringField('Picture', [validators.InputRequired(), validators.URL()])
class FacebookPostTemplateForm(Form):
name = fields.StringField('Name', [validators.InputRequired()])
title = fields.TextAreaField('Title', [validators.InputRequired()])
description = fields.TextAreaField('Description', [validators.InputRequired()])
linkUrl = fields.StringField('Link', [validators.InputRequired(), validators.URL()])
pictureUrl = fields.StringField('Picture', [validators.InputRequired(), validators.URL()])
translations = MyAppFieldList(fields.FormField(FacebookPostTranslationForm), [validators.Optional()])
class FacebookPostTemplateCreateForm(FacebookPostTemplateForm):
pass
class FacebookPostTemplateUpdateForm(FacebookPostTemplateForm):
pass
and then inside HTML template I have:
<span id="translationFieldsetHolder">
{% for translation in form.translations.entries %}
<fieldset class="translationFieldset">
<legend>{% if translation.language.data %}{{ translation.language.data|upper }}{% else %}Add{% endif %} Translation</legend>
{{ render_field(translation.language) }}
{{ render_field(translation.title, rows=5) }}
{{ render_field(translation.description, rows=5) }}
{{ render_field(translation.linkUrl) }}
{{ render_field(translation.pictureUrl) }}
{{ translation.csrf_token }}
</fieldset>
{% endfor %}
</span>
Everything but translation.description renders fine. Namely this line causes problems:
{{ render_field(translation.description, rows=5) }}
If I replace description with title it works fine (although it renders title twice instead of title plus description).
With description I get the following error:
{{ field.label(class="control-label") }}
UndefinedError: 'unicode object' has no attribute 'label'
in the macro that starts with:
{% macro render_field(field) %}
<div class="control-group {% if field.errors %}error{% endif %}">
{{ field.label(class="control-label") }}
I'm quite new to Python or WTForms. Is there anything special with description field name that could cause some problems / clashes?
I can see that the data is correctly delivered to the controller via the web service. There is nothing suspicious in the value of this field.
The sample code to be run. It was extracted from the original sources so at some places it is a mess.
If I change the name of the field it works :(
But it implies I would have to either change it on the web service or somewhere in Python code.
Thank you.
Field itself has description kwarg
class Field(object):
def __init__(self, label=None, validators=None, filters=tuple(),
description='', id=None, default=None, widget=None,
_form=None, _name=None, _prefix='', _translations=None):
What you are doing is adding into FormField(Field) object that also has description inside
class FacebookPostTranslationForm(Form):
description = fields.TextAreaField('Description', [validators.InputRequired()])
In template when You call translation.description WTForms is returning Field.description instead of FacebookPostTranslationForm.description
Hope that helps :)
I want to show a title and description from a db query in each form, but I don't want it to be in a charfield, I want it to be html-formatted text.
sample template code:
{% for form, data in zipped_data %}
<div class="row">
<div class="first_col">
<span class="title">{{ data.0 }}</span>
<div class="desc">
{{ data.1|default:"None" }}
</div>
</div>
{% for field in form %}
<div class="fieldWrapper" style="float: left; ">
{{ field.errors }}
{{ field }}
</div>
{% endfor %}
{% endfor %}
Is this the most idiomatic way of doing this? Or, is there a way to add text that will not be displayed inside of a textarea or text input to my model:
class ReportForm(forms.Form):
comment = forms.CharField()
?
Instead of zipping your forms with the additional data, you can override the constructor on your form and hold your title/description as instance-level member variables. This is a bit more object-oriented and learning how to do this will help you solve other problems down the road such as dynamic choice fields.
class MyForm (forms.Form):
def __init__ (self, title, desc, *args, **kwargs):
self.title = title
self.desc = desc
super (MyForm, self).__init__ (*args, **kwargs) # call base class
Then in your view code:
form = MyForm ('Title A', 'Description A')
Adjust accordingly if you need these values to come from the database. Then in your template, you access the instance variables just like you do anything else, e.g.:
<h1>{{ form.title }}</h1>
<p>{{ form.desc }}</p>
From the way you phrased your question, I think you probably have some confusion around the way Django uses Python class attributes to provide a declarative form API versus instance-level attributes that you apply to individual instances of a class, in this case your form objects.
Check out this link for a good discussion on the distinction
And this one
I just created a read-only widget by subclassing the text input field one:
class ReadOnlyText(forms.TextInput):
input_type = 'text'
def render(self, name, value, attrs=None):
if value is None:
value = ''
return value
And:
class ReportForm(forms.Form):
comment = forms.CharField(widget=ReadOnlyText, label='comment')
I had to solve a similar problem and like your idea Andrei. I had some issues using it though, as, if there were validation errors, the value of the read-only field would get lost. To solve this, I did something similar but overrode HiddenInput instead and kept the value in a hidden form field. ie:
class ReadOnlyText(forms.HiddenInput):
input_type = 'hidden'
def render(self, name, value, attrs=None):
if value is None:
value = ''
return mark_safe(value + super(ReadOnlyTextWidget, self).render(name, value, attrs))
class ReportForm(forms.Form):
comment = forms.CharField(widget=ReadOnlyText, label='comment')
I think you can get it with "{{ field.value }}". Maybe it's the easier way.
{% for form in formset %}
{% for field in form %}
{% if forloop.counter = 1 %}
<td><img src="{{ MEDIA_URL }}{{ field.value }}"/></td>
{% endif %}
{% if forloop.counter = 2 %}
<td>{{ field.value }}</td>
{% endif %}
{% if forloop.counter > 2 %}
<td>{{ field }}{{ field.errors }}</td>
{% endif %}
{% endfor %}
{% endfor %}