Django Crispy forms and i18n in HTML Layout - django

I tried searching but could not find any answer to this, in Django crispy forms there is an HTML layout Object which allows you to inject customized HTML into your form, I am working on a CreateView Form which implements some arbitary creation of related elements, I inject the HTML under my field the following way:
HTML(
'''<p class="add">
{% trans 'Add a category' %}
</p>'''
),
My problem is that although url tags work ok, trans tags are not parsed within crispy forms, is there an alternative (maybe within crispy forms?) to maintain i18n?
In the form template there is proper i18n loading of tags:
{% load i18n %}
{% load crispy_forms_tags %}

Since you are trying to do it in some .py file (as i understood), then why do you bother with templatetags - use python.
injected_html = u"<p class='add'><a href='%(url)s'>%(translation)s</a></p>" % {'url':some_get_url_method(), 'translation':_(u"Add a category")}
HTML(injected_html)

Related

Additional field shows up outside table in admin change_list view

I have a model called Project in an app called projects that I registered with the admin site so the instances can be added/edited/etc. This works as expected. Now I want to add a button for each project in the change list view on the admin site, that links to a custom form that requires a Project instance to do things. I followed a bunch of different tutorials to customize the admin site and managed to add another field to the table of the change list view. However the entries show up outside the table (see image).
I added the custom field by overwriting the admin/change_list.html template and calling a custom template tag custom_result_list within it. This tag adds a table field to the change list and then calls the admin/change_list_results.html template to render it. I have confirmed with a debugger that the item is added to the entries of the change list before the template is rendered (see image).
I cannot explain why the table is not rendered correctly even though the additional field has the same structure as the auto-generated ones. I have to admit I have resorted to Cargo Cult Programming, because I do not understand how this is supposed to work, despite spending too many hours trying to solve this simple problem.
Here's the relevant code.
In file /projects/templatetags/custom_admin_tags.py:
from django import template
from django.contrib.admin.templatetags.admin_list import result_list as admin_result_list
def custom_result_list(chl):
extended_cl = {}
extended_cl.update(admin_result_list(chl))
extended_cl["result_headers"].append({
'class_attrib': r' class="column-__str__"',
'sortable': False,
'text': 'Configure Project'
})
idx = 0
snippet = '<td class="action-button">{}</td>'
for project in chl.result_list:
extended_cl["results"][idx].append(snippet.format(project.id, project.unmod_name))
idx += 1
return extended_cl
register = template.Library()
register.inclusion_tag('admin/change_list_results.html')(custom_result_list)
In file templates/admin/projects/project/change_list.html:
{% extends "admin/change_list.html" %}
{% load i18n admin_urls static admin_list %}
{% load custom_admin_tags %}
{% block result_list %}
{% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %}
{% custom_result_list cl %}
{% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %}
{% endblock %}
To fix your issue:
from django.utils.html import format_html
replace your snippet.format(...) with format_html(snippet,...)
Explanation:
in django, all strings you pass from python are automatically HTML escaped. which here means, all your tags will not be considered as HTML. Such limitation is added to avoid any potential exploits by hackers. In your case, use of a template to render html is highly recommended. However, you can also send raw html from python using format_html helper function.

Wagtail not pulling through custom field panels

I'm overriding the wagtail AbstractFormField panel attribute in the following way:
...
before_input = RichTextField(verbose_name=_('before input'), blank=True)
after_input = RichTextField(verbose_name=_('after input'), blank=True)
panels = [
FieldPanel('label'),
FieldPanel('before_input'),
FieldPanel('after_input'),
FieldPanel('required'),
FieldPanel('field_type', classname="formbuilder-type"),
FieldPanel('choices', classname="formbuilder-choices"),
FieldPanel('default_value', classname="formbuilder-default"),
]
where the other panels are what comes out of the box.
This is working perfectly on the admin side and also saving as rich text into my database
I am pulling this through to my form in my template in the following way:
<form action="{% pageurl page %}" method="POST" class="lm-ls1" id="feedback-form">
{% csrf_token %}
{{ form.question1.help_text }} <!-- Simpler non interable way -->
{{ form.question1.before_input }}
<p>---------------</p>
{% for row in form.fields.values %}
{{row.choices}}
<p>---------------</p>
{{row.help_text}}
<p>---------------</p>
{{row.before_input}}
{% endfor %}
</form>
But I am only getting html output for the form panels excluding the before_input and after_input ones
I am getting through roughly the following:
Overall, how did you feel about the service you received today?
---------------
[('Very satisfied', 'Very satisfied'), ('Satisfied', 'Satisfied'),
('Neither satisfied nor dissatisfied', 'Neither satisfied nor dissatisfied'), ('Dissatisfied', 'Dissatisfied'), ('Very dissatisfied', 'Very dissatisfied')]
---------------
Overall, how did you feel about the service you received today?
---------------
---------------
How can I access the before_input field panel data stored in the _formfield wagtail table?
Bit late but hopefully this still helps you or someone else out there.
How Wagtail Forms Work
Wagtail forms provided to the view context for AbstractFormPage models is a fully instanced Django Form. This means that you will only ever find values in the form that can be given to a Django Form.
This includes fields, which are instances of Django's Fields (eg. CharField) and there is no simple way to add additional attributes to these fields.
You can see how the Form object is built in the Wagtail FormBuilder class definition.
1 - Make a Custom Template Tag
A somewhat simple way to get additional attributes on your FormField (Wagtail's FormField) is using a template tag.
Create a new file in in a folder templatetags in your app, and build a simple_tag that will take the form_page, the field (which will be a Django Field instance) and a string of the attribute name you want to get.
# myapp/templatetags/form_tags.py
from django import template
from django.utils.html import mark_safe
register = template.Library()
#register.simple_tag(name='form_field_attribute')
def form_field_attribute(form_page, field, attribute_name, default=None):
"""Return attribute on FormField where field matches 'field' provided."""
# field is a django Field instance
field_name = field.name
results = [
# if html is stored, need to use mark_safe - be careful though.
mark_safe(getattr(form_field, attribute_name, default))
# get_form_fields() is a built in function on AbstractFormPage
for form_field in form_page.get_form_fields()
# clean_name is property on AbstractFormField used for Django Field name
if form_field.clean_name == field_name]
if results:
return results[0]
return default
2 - Revise your form_page.html Template
In your template, cycle through your form (this is the Django Form instance) and use the template helper to get you the extra attributes you need. Example below, passing in page or self will work the same as they are both the instance of your FormPage.
<form action="{% pageurl page %}" method="POST" role="form">
{% csrf_token %}
{% for field in form %}
<div>{% form_field_attribute page field 'before_input' %}</div>
{{ field }}
<div>{% form_field_attribute page field 'after_input' %}</div>
{% endfor %}
<input type="submit">
</form>

Apply filter to result of Django custom template tag (like i18n trans)

We have a Django project with a legacy translation module that stores translations in the database, and does not use Django i18n (gettext).
We have written custom template tags {% db_trans ... %} that work like Django's default {% trans ... %}, but there is one trick we cannot duplicate.
In this example, "tree" (English) translates to "boom" (Dutch).
In my Django template, if I use default i18n and write {% trans "tree" %} I will see the result boom. When I want to use this as a title, I use the capfirst filter like this {% trans "tree"|capfirst %}, and the result will be Boom. Note the capital B.
This seems to be a special trick. Our db_trans custom tag (based on simple_tag) capitalizes the input before translation. Since there is no translation in the database for Tree, {% db_trans "tree"|capfirst %} renders its default (untranslated) value, Tree.
I now about {% filter capfirst %}...{% endfilter %} and will probably use it if we cannot find an easy solution.
I have checked the Django source code and I saw that {% trans ... %} is not based on simple_tag and does quite a lot of text argument parsing.
My question: is there a package or snippet that allows a filter specified as shown above to be applied to the result of a custom tag?
If you make your template tag a filter, then you can chain it with capfirst built-in filter
from django import template
register = template.Library()
#register.filter
def db_trans(word, request):
return do_the_translation(word, request)
Then in the html it would look like this
{{ 'tree'|db_trans:request|capfirst }}

Should I override a template for a Django (comments) framework or do the customization inside my own templates?

I am trying to customize the output of the comments list and forms using Django's comments framework.
Inside my own template, should I try to customize the comments by doing something like {% get_comment_form for object as form %} and carefully construct the form based on the form variable or should I override the form.html template and simply call {% render_comment_form for object %}?
Currently I'm leaning more toward using {% get_comment_form for object as form %} inside my own template, but using form.html as a guideline to write my own form.
On my websites, overriding form.html (and other comments templates) and simply calling {% render_comment_form for object %} works very well.
You should definitively have a form.html that is customized for your website's design. And in case you want a comment form to have a particular design, you can use get_comment_form.

breadcrumbs using django admin templates

I have created a site mainly using django's admin interface, plus a few custom views. As the majority of the site is using the admin (and I am not to hot with css), I have just used django's admin tempates for my custom views (they are extended generic views).
Anyway, most of my custom views look good, and match the look and feel of the admin interface, but I don't know how to get the breadcrumbs working.
So form an extended generic view, how and what do I pass to the tempate's
{% block breadcrumb %}
tag?
I saw one article that mentioned the context object, but didn't have any further details.
If you want to provide breadcrumbs in your template and get breadcrumbs from parent template you can use block breadcrumbs & block.super variable in it:
{% block breadcrumbs %}{{ block.super }} › My custom site{% endblock %}
Or just pass to the template variable title.