How do I amend a form template in Django? - django

Im using a custom/extended ModelMultipleChoiceField and ModelChoiceIterator as per the answer to another question here
https://stackoverflow.com/a/73660104/20600906
It works perfectly but Im trying to amend the template used to generate the groups to apply some custom styling - but after poking around everywhere I cant find anything to overwrite.
I have gone through all the template files in
'\venv\Lib\site-packages\django\forms\templates\django\forms' and cannot find anything relating to the behaviour Im seeing on screen.
The 'groups' are wrapped in strong tags which is fine, but id like to find the file specifying this so I can overwrite with some bootstrap classes.
I've traced back through the class inheritance and the first widget specifiying a template is this one:
class Select(ChoiceWidget):
input_type = "select"
template_name = "django/forms/widgets/select.html"
option_template_name = "django/forms/widgets/select_option.html"
add_id_index = False
checked_attribute = {"selected": True}
option_inherits_attrs = False
Checking these files doesn't help as they don't appear to be the ones in use. Not sure where i'm going wrong?
Edit: Ive just realised the strong tags are coming from the cripsy forms tag. The templates probably make a lot more sense now, Ill go do some more digging.

Related

Django: a custom template tag to convert links inside of a TextField and change the hyperlink text

The scenario is that there are some dynamic texts on some templates, that will contain hyperlinks.
For this, I have a SiteDataKeyValue model, in which the dynamic texts for different parts of the template are inputted. This is the model:
class SiteDataKeyValue(models.Model):
key = models.CharField(
max_length=200, verbose_name="نام متن مورد نظر", unique=True
)
value = models.TextField(verbose_name="متن")
def __str__(self):
return self.key
A solution that I've found already, is Django urlize template tag. As mentioned in the docs, this tag converts texts like https://www.google.com to www.google.com, which is nice but not what I'd like to achieve. I want to be able to change the hyperlink text, so the output would be something like: Click Here!.
I searched for a bit, came across modules like bleach, which is a fine module, but I couldn't find the answer I was looking for (I skimmed through the docs and there was nothing about the hyperlink text).
Also I saw a comment somewhere telling that this could be achieved by writing a custom Django template tag, but although I tried to do this regarding the custom template filters docs, I didn't have a clue to how to achieve this.
I'm not asking for the code, although it would be really appreciated if you provide instructions for writing this custom template tag, or better, if you could point me to something like this that is already out there.
First of all you can extend urlize tag like the answer in this
or you can change the main code which you can find it in django.utils.html and override its url variable to change it.
But I think the best method is extending the urlize tag
like this:
{% text | urlize | change_a_text_filter:{{ dome_new_a_text }} %}
then you can scrape the text and use regex to find >sample-text</a> then you can change it to the argument that defines in your tag
from django import template
register = template.Library()
#register.simple_tag
def change_a_text_filter(format_string, arg):
# find the url that made in urlize with regex
# change it with arg
# return the result
I was on a completely wrong road to solve this problem. I was trying to urlize a link from TextField, but didn't consider the fact that I only needed to implement html code as Visit link.com! in the TextField, and then use safe template tag to render html directly as below:
{{ text.value|safe }}
So in this solution, there is no need to urlize, and of course there is no need to extend this tag neither.
NOTE: As commented by #rahimz (link to comment) I understand that there are safety concerns regarding safe tag, So I should emphasize that only me and a company-trusted admin will have access to admin panel and there is no worries that this admin will send malicious code through this TextField.

Django include template tag multiple template choices

I have a requirement where in I have to include a template inside a template tag. But the template I want to include is not fixed. I have list of templates and whichever is found first need to be included.
The include template tag however takes only a single template name.
Is there a way to modify this behavior or any other way to achieve this.
I was using custom template tag for the same, but I am getting warning since updating to django 1.9 that
RemovedInDjango110Warning: render() must be called with a dict, not a RequestContext.
return t.render(context)
This is my custom template tag
#register.simple_tag(takes_context=True)
def custom_include(context, *template_choices):
template_choices = (context['optionalTemplate'],) + template_choices
t = template.loader.select_template(template_choices)
return t.render(context)
I tried getting dict from context, and it seemed to work (atleast I thought that). But some of the context data went missing. For example my google analytics hits went to zero and that sort of stuff.
request = context['request']
mydict = context.dicts[0]
return t.render(mydict, request)
Can anyone suggest how to achieve this.
You need to flatten the context into a single dict. This will merge the data from all the internal dicts:
mydict = context.flatten()
return t.render(mydict, context.request)
Also check the docs.

Django Grappelli Rearrange Inlines id override

According to the docs:
http://django-grappelli.readthedocs.org/en/latest/customization.html#rearrange-inlines
The two classes for the placeholder are important. First, you need a class placeholder. The second class has to match the id of the inline–group.
All's well and good, I was able to set up my inlines fine, my issue is now - where does grappelli get the "id of the inline group" I can't find any reference, and pouring through the source code is offering me no solace.
Simply, I want to change the element-id that grappelli is using. Currently, it looks to me that it is taking the object name itself and converting to a lowercase name and appending set to the end. Do we have access to override the "id of the inline-group"?
Also, I am not 100% sure exactly how (or where) grappelli is doing this, it is definitely not documented... at all in fact.
Any help would be much appreciated.
It is the id of the inline element on HTML page. You can check the id of the default HTML inline element.
<div id="[related_name of ForeignKey]-group">
For example:
If in model "MyModel2", you have a ForeignKey like this:
my_model_1 = models.ForeignKey(MyModel1, related_name='my_model_2')
Then the id should be "my_model_2-group".
The id of the inline group is set in grappelli/templates/admin/edit_inline, in stacked.html line 5, or tabular.html line 6 (depending on which type of inline you're usng):
id="{{ inline_admin_formset.formset.prefix }}-group" >
You can override this by copying the file (stacked.html or tabular.html) into your template directory and setting the variable "template" to the file's new location e.g.:
# admin.py
class MyModelInline(admin.StackedInline):
template = 'path/to/stacked.html'
...
Then edit whatever you want in e.g. stacked.html.
I don't know if this is the best-practices way of doing this, but it's similar to what's done in the django tutorial.

How to set attribute on widget in flask-wtf?

I am using a TextArea provided by Flask-WTF.
numbers = forms.TextField('Numbers', [forms.validators.Required()], widget=forms.widgets.TextArea())
I want to change cols of the resulting fields to say 80. How do I accomplish this. I don't want to do it in template and would like to do this in form.
Django provides:
name = forms.TextInput(attrs={'size': 10, 'title': 'Your name',})
I want something similar.
You can do it during output in your html
like: form.numbers(cols=20)
Oh, sorry did not notice that you mentioned template.
Update:
Actualy also had this problem, my solution was to extend Form class and add dictionary to form objects.
So all custom attributes go into that dictionary and used on output. This can be done with custom template macros or redefining render methods

How to make a Template extend another one?

I've got some templates which I'm building from some data I've got stored in my DB:
my_template = Template(string_data)
Now I want that template to extend another one (also stored as text in the DB). How can I do that? Looking for something like:
my_template.base = other_template
Or whatever the syntax is. Can't find documentation on this.
I see template.nodelist in the source; can I maybe prepend some kind of extends node?
Added a bounty... will grant to first person who can tell me how to do this without hacking the core of django.
No need to hack the core of Django. Creating your own template loader, as Ignacio recommends, is pretty simple. You just need to subclass django.template.loader.BaseLoader, and implement the load_template_source method. This just takes a string for the template name, which you can simply use to look up the item in the database and return the string.
In your case, since you've got two template elements in the the single db row, you might want to do something to specify it in the extends tag:
{% extends "myinstance.html_version" %}
and simply split that in load_template_source:
def load_template_source(self, template_name, template_dirs=None):
name, field = template_name.split('.')
tpl = TemplateModel.objects.get(name=name)
return getattr(tpl, field)
Of course you'd want to put some error handling in there, but you get the idea. You just then specify this template loader in your settings TEMPLATE_LOADERS tuple.
Edit after comment I still don't really understand why, but if all you want to do is to choose the template to extend from dynamically, why not just insert that in the text template before processing?
tpl_string = get_template_from_wherever()
extender = '{% extends "%s" %}' % template_name_to_extend
tpl = Template(extender + tpl_string)
Still a hack, but probably much less of one than mucking about with the internals of template inheritance.
To restate your problem, you want to:
Take text that you've already got in a string (because you loaded it from a database),
Take more text that you're going to load into a string (because you know where to load it from),
Make a parent template out of the second string,
Make a child template out of the first string,
Set the parent of the child template (from the first string) to be the parent template (from the second string),
Evaluate the child template.
Most importantly, you want to avoid having to have anything like {% extends ...%} in the child template, because ... why?
And finally, you want to do this without hacking the core of Django.
Short answer:
Not possible. Out of the box, Django won't do what you're asking.
Long answer:
The entire concept of template inheritance in Django is implemented through the extends tag. More accurately, it's implemented through the ExtendsNode class of the django.template.loader_tags module, which is created when the extends tag is parsed. When you create a Template(), it parses its source string, and creates a list of Nodes (stored in the template's nodelist as you noted earlier). At a later date, you can render the template using whatever context you like, as many times as you like.
Roughly, rendering works by calling render() on each node in the nodelist. If the first node in a template's nodelist is an ExtendsNode (and it must be the first node), then template inheritance magic happens. When the ExtendsNode is created, it is given the template's nodelist, and a parent name (either as a string (parent_name) or expression (parent_name_expr). When the ExtendsNode is rendered, it will use its get_parent() method to call get_template(parent_name), which will invoke the template loader mechanism to load the parent template. Once it has the parent template, the ExtendsNode::render() method will work the magic of template inheritance.
Feel free to check out the code yourself.
In order to avoid using a template loader, you would have to do the following:
Create a class SpecialExtendsNode(ExtendsNode), overriding the __init__ method and the get_parent method.
After creating a template from the child string, create an instance of your subclass, initialized from the parent template.
Insert your instance of SpecialExtendsNode into the head of the child template's nodelist.
Pray that none of this is ever changed by the Django developers.
Just to make things easy for you, here's your class:
class SpecialExtendsNode(ExtendsNode):
def __init__( self, nodelist, parent, name ):
self.myparent = parent
ExtendsNode.__init__( self, nodelist, name, None, None )
def get_parent( self ):
return self.myparent
And the code to use it will look like this:
parent = Template( parent_string )
child = Template( child_string )
hack = SpecialExtendsNode( child.nodelist, parent, "parent name" )
child.nodelist.insert( 0, hack )
output = child.render( context )
Now that I've spent the time and effort to give you a gun, and load it with bullets, I'm going to try to convince you to not to pull the trigger, but instead to do things the way everyone else has recommended.
The code I've written here has no error checking, and was written against Django 1.2. While I haven't tested it, I'm 99% sure that it will work on Django 1.2. I have no idea if it will work on any other version of Django. On the other hand, aside from providing the source, the Django developers haven't documented the internals of their template processor, other than to provide documented interfaces for writing new template tags and filters, and a documented interface for writing template loaders (specifically mentioning the use case of loading a template from a database). That means that there is a reasonable case that someday the Django developers may choose to rewrite or heavily modify the template extension mechanism. If they do this, I will 100% guarantee that this code will break. Why? Because it's a hack. This is what hacking the core of Django looks like. It will work for a day, or a week, or even months at a time, but sooner or later, you (or the programmer that comes after you) will upgrade from Django 1.2 to a later version, and then it will break. And when it breaks, I won't be there to help you, and the Django developers will all ask, why didn't you just write a template loader?
Now tell me, which involves more hacking the core of Django -- writing a template loader, which is documented and supported by the developers, or what I just described?
Unfortunately you're going to need to write your own template loader to read the database. The {% extends %} template tag is usually used for this, but it defers the task of actually loading the templates to the loaders.
I think you'll need to subclass django.template.loader_tags.ExtendNode and override its get_parent method, so that you can use your own get_template in there! Then you should be able to add the ExtendNode to your template.nodelist, but also be aware that it has to be the first!
from app.models.misc import EmailTemplate
from django.template import TemplateDoesNotExist
from django.template.loader import BaseLoader
class Loader(BaseLoader):
is_usable = True
def load_template_source(self, template_name, template_dirs=None):
try:
key, ext = template_name.split('.')
attr = {
'html': 'html_content',
'txt': 'text_content',
}[ext]
tpl = EmailTemplate.objects.get(key=key)
return (getattr(tpl, attr), repr(tpl))
except:
raise TemplateDoesNotExist
Really hard to write when you can't find updated documentation :\