Is there a way to use the {{date|timesince}} filter, but instead of having two adjacent units, only display one?
For example, my template is currently displaying "18 hours, 16 minutes". How would I get it to display "18 hours"? (Rounding is not a concern here.) Thank you.
I can't think of a simple builtin way to do this. Here's a custom filter I've sometimes found useful:
from django import template
from django.template.defaultfilters import stringfilter
register = template.Library()
#register.filter
#stringfilter
def upto(value, delimiter=None):
return value.split(delimiter)[0]
upto.is_safe = True
Then you could just do
{{ date|timesince|upto:',' }}
Since the timesince filter doesn't accept any arguments, you will have to manually strip off the hours from your date.
Here is a custom template filter you can use to strip off the minutes, seconds, and microseconds from your datetime object:
#this should be at the top of your custom template tags file
from django.template import Library, Node, TemplateSyntaxError
register = Library()
#custom template filter - place this in your custom template tags file
#register.filter
def only_hours(value):
"""
Filter - removes the minutes, seconds, and milliseconds from a datetime
Example usage in template:
{{ my_datetime|only_hours|timesince }}
This would show the hours in my_datetime without showing the minutes or seconds.
"""
#replace returns a new object instead of modifying in place
return value.replace(minute=0, second=0, microsecond=0)
If you haven't used a custom template filter or tag before, you will need to create a directory in your django application (i.e. at the same level as models.py and views.py) called templatetags, and create a file inside it called __init__.py (this makes a standard python module).
Then, create a python source file inside it, for example my_tags.py, and paste the sample code above into it. Inside your view, use {% load my_tags %} to get Django to load your tags, and then you can use the above filter as shown in the documentation above.
Well, you can make use of JS here. You need to add a script that splits the first and second units and then displays only the first one.
Say the part of your template looks like this:
<your_tag id="your_tag_id">{{date|timesince}}</your_tag>
Now, add the below script to your template.
<script>
let timesince = document.getElementById("your_tag_id").innerHTML.split(",");
document.getElementById("your_tag_id").innerHTML = timesince[0];
</script>
A quick and dirty way:
Change the django source file $PYTHON_PATH/django/utils/timesince.py #line51(django1.7) :
result = avoid_wrapping(name % count)
return result #add this line let timesince return here
if i + 1 < len(TIMESINCE_CHUNKS):
# Now get the second item
seconds2, name2 = TIMESINCE_CHUNKS[i + 1]
count2 = (since - (seconds * count)) // seconds2
if count2 != 0:
result += ugettext(', ') + avoid_wrapping(name2 % count2)
return result
Related
I come from Ruby on Rails and I am trying to learn Django. There are multiple things that puzzle me when it comes to how to solve simple things. I have a sidebar in which I would like to have a link to a random item.
My first thought was to use:
Random headline
Although it works perfectly fine to run Headline.objects.order_by('?')[0].id in the shell, this causes an error in Django:
Could not parse the remainder: '('?')[0].id' from 'Headline.objects.order_by('?')[0].id'
which I find very weird since 'Headline.objects.order_by('?')[0].id' creates a number (id) and if I put the same number in as
Random headline
it works perfectly fine.
Another option would be to create this previously in the template, like
{% rand_id = Headline.objects.order_by('?')[0].id %}
Random headline
which is I would hotfix it in Rails - but this doesn't work. The third option (which is better than the 2nd) is to put the variable in the view. However, this is not really viable since this code is in the sidebar and I cannot pass this in every view (DRY!).
So, I figure out I should use context templates, which I - after reading up on it find overly complicated for something as simple as this.
Is there really no good, simple way to solve this problem? Or am I "thinking too much rails"?
In your case a custom template tag would be just fine.
https://docs.djangoproject.com/en/4.0/howto/custom-template-tags/#writing-custom-template-tags
from django import template
from django.utils.html import format_html
register = template.Library()
#register.simple_tag
def random_headline_link():
random_headline = Headline.objects.order_by('?')[0]
headline_url = reverse('headline_detail', args=[random_headline.id,])
return format_html('Random headline', url=headline_url)
{% random_headline_link %}
<!-- the rest is not needed anymore, as it is all coupled together in the templatetag -->
Or if you only like/need the URL, you could use the following templatetag code.
from django import template
register = template.Library()
#register.simple_tag
def random_headline_url():
random_headline = Headline.objects.order_by('?')[0]
return reverse('headline_detail', args=[random_headline.id,])
Random headline
For now on I have in my template a paragraph like this <p class="...">{{ post.content }}</p> and if this Post's content contains a link or #hashtag it is rendered as a normal text with the rest of the post. How can I customize it? For example change text-color and add tag around it?
As I said in comment, you can use custom tag filter to wrap your content, and use Regular Expression to generate links and hashtags
Create your tags file, and name it as you want:
tag_filter_name.py
If you're not familiar with custom tag filter creation, you can learn more about it in the Official Documentation
from django import template
import re
register = template.Library()
def generate_link(link):
return '<a class="link" href="{}">{}</a>'.format(link, link)
def generate_hashtag_link(tag):
# Free to configuree the URL the way adapted your project
url = "/tags/{}/".format(tag)
return '<a class="hashtag" href="{}">#{}</a>'.format(url, tag)
And then, you create the function what will be used as tag filter
#register.filter
def render_content(obj):
text = re.sub(r"#(\w+)", lambda m: generate_hashtag_link(m.group(1)),obj)
return re.sub(r"(?P<url>https?://[^\s]+)", lambda m: generate_link(m.group(1)),text)
If you want Django to mark it as safe content, you can do the following:
from django.utils.safestring import mark_safe # import function
''' function codes here '''
return mark_safe(re.sub(r"(?Phttps?://[^\s]+)",
lambda m: generate_link(m.group(1)),text))
And finally, to use it in your template, don't forget to load it
{% load tag_filter_name %}
{{ post.content|render_content }}
Best way: custom tag filters here is the docs URL
https://docs.djangoproject.com/en/2.2/ref/templates/builtins/
Good way: If you know JS create a function that handles the formatting on the front end
in HTML:
onload="myfunction("{{post.content}}")"
in JS sort for the string containing the # wrap it in a span or other element and style away. then replace the inner HTML with your new formatted piece. This will save rendering time on the server and also frees you up from having to loop thru the list of posts in the view
Ok way: not preferred but if you hate js and only want to work in python (understandable). You need to loop through the list of posts separate out the items of the post format them the way you like with inline style. then add them to a new object that you will append to the end of a new list of posts that you will then pass thru to context. This is a real pain please don't do this if you can help it at all.
the tag filters are awsome take advantage but if they won't work for your use case I would highly advise using vanilla JS
Using Jinja2, how do I format a date field? I know in Python I can simply do this:
print(car.date_of_manufacture.strftime('%Y-%m-%d'))
But how do I format the date in Jinja2?
There are two ways to do it. The direct approach would be to simply call (and print) the strftime() method in your template, for example
{{ car.date_of_manufacture.strftime('%Y-%m-%d') }}
Another, sightly better approach would be to define your own filter, e.g.:
from flask import Flask
import babel
app = Flask(__name__)
#app.template_filter()
def format_datetime(value, format='medium'):
if format == 'full':
format="EEEE, d. MMMM y 'at' HH:mm"
elif format == 'medium':
format="EE dd.MM.y HH:mm"
return babel.dates.format_datetime(value, format)
(This filter is based on babel for reasons regarding i18n, but you can use strftime too). The advantage of the filter is, that you can write
{{ car.date_of_manufacture|format_datetime }}
{{ car.date_of_manufacture|format_datetime('full') }}
which looks nicer and is more maintainable. Another common filter is also the "timedelta" filter, which evaluates to something like "written 8 minutes ago". You can use babel.dates.format_timedelta for that, and register it as filter similar to the datetime example given here.
Here's the filter that I ended up using for strftime in Jinja2 and Flask
#app.template_filter('strftime')
def _jinja2_filter_datetime(date, fmt=None):
date = dateutil.parser.parse(date)
native = date.replace(tzinfo=None)
format='%b %d, %Y'
return native.strftime(format)
And then you use the filter like so:
{{car.date_of_manufacture|strftime}}
I think you have to write your own filter for that. It's actually the example for custom filters in the documentation.
If you are dealing with a lower level time object (I often just use integers), and don't want to write a custom filter for whatever reason, an approach I use is to pass the strftime function into the template as a variable, where it can be called where you need it.
For example:
import time
context={
'now':int(time.time()),
'strftime':time.strftime } # Note there are no brackets () after strftime
# This means we are passing in a function,
# not the result of a function.
self.response.write(jinja2.render_template('sometemplate.html', **context))
Which can then be used within sometemplate.html:
<html>
<body>
<p>The time is {{ strftime('%H:%M%:%S',now) }}, and 5 seconds ago it was {{ strftime('%H:%M%:%S',now-5) }}.
</body>
</html>
You can use it like this in template without any filters
{{ car.date_of_manufacture.strftime('%Y-%m-%d') }}
Google App Engine users : If you're moving from Django to Jinja2, and looking to replace the date filter, note that the % formatting codes are different.
The strftime % codes are here: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
You can use it like this in jinja template
{{ row.session_start_date_time.strftime('%d-%m-%Y %H:%M:%S')}}
In this code the field name is row.session_start_date_time.
in flask, with babel, I like to do this :
#app.template_filter('dt')
def _jinja2_filter_datetime(date, fmt=None):
if fmt:
return date.strftime(fmt)
else:
return date.strftime(gettext('%%m/%%d/%%Y'))
used in the template with {{mydatetimeobject|dt}}
so no with babel you can specify your various format in messages.po like this for instance :
#: app/views.py:36
#, python-format
msgid "%%m/%%d/%%Y"
msgstr "%%d/%%m/%%Y"
I use this filter, it's in Spanish but you may change the names as you need.
#app.template_filter('datetime')
def date_format(value):
months = ('Enero','Febrero',"Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre")
month = months[value.month-1]
hora = str(value.hour).zfill(2)
minutos = str(value.minute).zfill(2)
return "{} de {} del {} a las {}:{}hs".format(value.day, month, value.year, hora, minutos)
There is a jinja2 extension you can use just need pip install (https://github.com/hackebrot/jinja2-time)
I want to use more than one filter on template like below:
value: {{ record.status|cut:"build:"|add:"5" }}
where record.status would be build:n, 0 < n< 100
but I want to add this value a base value 5.
I tried above code, it only take effect on the first filter,
so I did not get the value plus 5.
Does django only support one filter?
Thanks
First, the answer for your question "Does django only support one filter?" is that
Django does support almost unlimited number of chained filters (depends on your platform and ability to write that number of chained filters of course =) . Take some code for example (not proof but it makes sense), it is actually a template '{{ x|add:1|add:1|...10000 in all...|add:1 }}'
>>> from django.template import *
>>> t = Template('{{ x|'+'|'.join(['add:1']*10000)+' }}')
>>> t.render(Context({'x':0}))
u'10000'
Second, please check the template to ensure that you are using built-in version of cut and add; also check the output value after the cut to ensure it can be coerced to int w/o raising exception.
I've just checked and found that even the Django 0.95 supports this usage:
def add(value, arg):
"Adds the arg to the value"
return int(value) + int(arg)
Chaining filters is supported. If you want to figure why it doesn't work, then what I'd do is:
install ipdb
in django/templates/defaultfilters.py, find "def add", and put "import ipdb; ipdb.set_trace()" at the top of the function
open the page in the browser again, you should be able to follow the execution of the code from the terminal that runs runserver and figure why you're not getting the expected results
An easier way is to make your own template filter. It could look like
from django.template import Library
register = Library()
#register.filter
def cut_and_add(value, cut, add):
value = value.replace(cut, '')
value = int(value) + add
return value
Suppose you saved this in yourapp/templatetags/your_templatetags.py (and that yourapp/templatetags/__init__.py exists - it can be empty). Then you would use it in the template as such:
{% load your_templatetags %}
{{ record.status|cut_and_add:"build:",5 }}
Of course, this is untested, pseudo code. But with a little effort you could get it to work.
Django has truncatewords template tag, which cuts the text at the given word count. But there is nothing like truncatechars.
What's the best way to cut the text in the template at given char-length limit?
This has recently been added in Django 1.4. e.g.:
{{ value|truncatechars:9 }}
See doc here
{{ value|slice:"5" }}{% if value|length > 5 %}...{% endif %}
Update
Since version 1.4, Django have a built-in template tag for this:
{{ value|truncatechars:9 }}
I made my own template filter, that add "..." to the end of (last word of) the (truncated) string as well:
from django import template
register = template.Library()
#register.filter("truncate_chars")
def truncate_chars(value, max_length):
if len(value) > max_length:
truncd_val = value[:max_length]
if not len(value) == max_length+1 and value[max_length+1] != " ":
truncd_val = truncd_val[:truncd_val.rfind(" ")]
return truncd_val + "..."
return value
If you go on creating your own custom template tag, consider to leverage the native Django util Truncator.
The following is a sample usage of the Truncator util:
>>> from django.utils.text import Truncator
>>> Truncator("Django template tag to truncate text")
<Truncator: <function <lambda> at 0x10ff81b18>>
>>>Truncator("Django template tag to truncate text").words(3)
u'Django template tag...'
Truncator("Django template tag to truncate text").chars(20)
u'Django template t...'
And here how you can use it inside a Django template tag:
from django import template
from django.utils.text import Truncator
register = template.Library()
#register.filter("custom_truncator")
def custom_truncator(value, max_len, trunc_words=False):
""" Set trunc_words=True to truncate by words instead of by chars."""
truncator = Truncator(value)
return truncator.words(max_len) if trunc_words else truncator.chars(max_len)
Eventually you can import the custom_truncator in any Django template.
Here it is in the Django Documentation, Built-in template tags and filters: truncatechars
You should write a custom template filter: http://docs.djangoproject.com/en/dev/howto/custom-template-tags/#writing-custom-template-filters
Have a look at how truncatewords is built in django.utils.text
You can achieve your goal with similar code:
{{ value_of_text|truncatechars:NUM_OF_CHARS_TO_TRUNCATE}}
where NUM_OF_CHARS_TO_TRUNCATE is number of chars to leave.
slice
Adding a "truncate" filter was a feature request for 4 years but finally landed in trunk, as far as I understand https://code.djangoproject.com/ticket/5025 - so we’ve to wait for the next release or use trunk.