I am using an unordered_list tag in django.
I have the following list:
foo = ['A', ['B', 'C', 'D'], 'E']
And the following tag:
{{ foo|unordered_list }}
Which produces, as expected the following:
<li>A
<ul>
<li>B</li>
<li>C</li>
<li>D</li>
</ul>
</li>
<li>E</li>
What I would like to do is to add id and class properties to each "li" node.
I understand that I can do it in JavaScript, or implement my own template tag in django. But I wanted to ask first. May be there already exists an easy build-in way to do it in django template?
As you can see in the source, the <li> is hardcoded so you can't do it without creating your own templatefilter.
def unordered_list(value, autoescape=None):
"""
Recursively takes a self-nested list and returns an HTML unordered list --
WITHOUT opening and closing <ul> tags.
The list is assumed to be in the proper format. For example, if ``var``
contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
then ``{{ var|unordered_list }}`` would return::
<li>States
<ul>
<li>Kansas
<ul>
<li>Lawrence</li>
<li>Topeka</li>
</ul>
</li>
<li>Illinois</li>
</ul>
</li>
"""
if autoescape:
from django.utils.html import conditional_escape
escaper = conditional_escape
else:
escaper = lambda x: x
def convert_old_style_list(list_):
"""
Converts old style lists to the new easier to understand format.
The old list format looked like:
['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
And it is converted to:
['Item 1', ['Item 1.1', 'Item 1.2]]
"""
if not isinstance(list_, (tuple, list)) or len(list_) != 2:
return list_, False
first_item, second_item = list_
if second_item == []:
return [first_item], True
old_style_list = True
new_second_item = []
for sublist in second_item:
item, old_style_list = convert_old_style_list(sublist)
if not old_style_list:
break
new_second_item.extend(item)
if old_style_list:
second_item = new_second_item
return [first_item, second_item], old_style_list
def _helper(list_, tabs=1):
indent = u'\t' * tabs
output = []
list_length = len(list_)
i = 0
while i < list_length:
title = list_[i]
sublist = ''
sublist_item = None
if isinstance(title, (list, tuple)):
sublist_item = title
title = ''
elif i < list_length - 1:
next_item = list_[i+1]
if next_item and isinstance(next_item, (list, tuple)):
# The next item is a sub-list.
sublist_item = next_item
# We've processed the next item now too.
i += 1
if sublist_item:
sublist = _helper(sublist_item, tabs+1)
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
indent, indent)
output.append('%s<li>%s%s</li>' % (indent,
escaper(force_unicode(title)), sublist))
i += 1
return '\n'.join(output)
value, converted = convert_old_style_list(value)
return mark_safe(_helper(value))
unordered_list.is_safe = True
unordered_list.needs_autoescape = True
It should be fairly easy to rewrite the filter to add id/class support though, depending on how you want it to work.
Related
I am trying to return the values from a Dict in Django.
My views.py prints the the correct data in the terminal when doing a GET-request to my page, however, in my template I only get the last line of the dictionary.
I have tried looping the dict in my template and all sorts of combinations but I can't seem to make it work. Am I missing something? For instance, in the template below I print the entire dict. But it still only prints the last row somehow.
views.py
fruits = [
'Apple', 'Banana', 'Orange']
for fruit in fruits:
price_change = historical_prices['price_change'][::-1]
low_price = 0
for day in price_change:
if day < -0.1:
low_price += 1
else:
break
if low_price >= 0:
ls_dict = {'fruit': fruit, 'low_price': low_price}
print(ls_dict)
return render(request, "prices/index.html", {
"ls_dict": ls_dict,
})
Template
<p>{{ ls_dict }}</p>
Template output
{'fruit': 'Orange', 'low_price': 1}
Correct print which views.py produces
{'fruit': 'Apple', 'low_price': 1}
{'fruit': 'Banana', 'low_price': 3}
{'fruit': 'Orange', 'low_price': 1}
The print() seems correct, but you are just overriding the variable over and over again, thus ls_dict will only be the set with the last iteration of the for loop.
You can test this by print(ls_dict) outside of the for-loop
Try the following:
ls_list = []
for fruit in fruits:
price_change = historical_prices['price_change'][::-1]
low_price = 0
for day in price_change:
if day < -0.1:
low_price += 1
else:
break
if low_price >= 0:
ls_dict = {'fruit': fruit, 'low_price': low_price}
ls_list.append(ls_dict)
return render(request, "prices/index.html", {
"ls_list": ls_list,
})
I am trying to return most voted answer for each question.also i want to send also extra infomation of that answer like vote and id.
Printing one value is easy but for more than one i have to return dictionary.So how can i return dictionary and print all values in template.
from django import template
register = template.Library()
#register.simple_tag
def getmostvotedanswer(answers):
answer = answers.order_by('-vote')[0]
answer_info = {
'answer':answer.answer,
'vote':answer.vote,
'id':answer.id
}
return answer_info
index.html
<p class="small text-muted ">{% getmostvotedanswer question.answer_set.all %}</p>
Output
{'answer': 'THIS IS ANSWER THIS IS ANSWER THIS IS ANSWER THIS IS ANSWER THIS IS ANSWER', 'vote': 7, 'id': 1}
I can call template_tag 3 times for three values.
But I don't want to call templatetag again and again i think it will affect performance.
view.py
def index(request):
questions = Question.objects.all()
context = {
'questions':questions
}
return render(request,'index.html',context=context)
Edit -> Add view.py
The easiest, debugable, best performance and test friendly way to achieve it is to cook data on view instead of write custom template tag. Windows functions are needed to get the first answer of each question:
from django.db.models import F, Window
from django.db.models.functions.window import FirstValue
def index(request):
#q_and_a_ids = [ (id question, id most voted answer), (... ]
q_and_a_ids = (
Question
.objects
.annotate(
most_voted_id=Window(
expression=FirstValue('answer__id'),
partition_by=['id'],
order_by=F('answer__vote').desc()
)
)
.distinct()
.values_list( 'id', 'most_voted_id')
)
answers_ids = set( [ a_id for (_,a_id) in q_and_a_ids] )
questions_dict = Question.objects.in_bulk()
answers_dict = Answers.objects.filter(pk__in=answers_ids).in_bulk()
#q_and_a = [ { 'q':question, 'a':most voted answer}, { ... ]
q_and_a = [ {'q': questions_dict[q_id],
'a': answers_dict.get(a_id) }
for (q_id,a_id) in q_and_a_ids ]
context = {
'questions_and_answers': q_and_a
}
return render(request,'index.html',context=context)
I have a view on my website that uses a couple of Django forms to allow the user to specify a date range. I was able to get it so that one Django form creates a start and end field and that when the user clicks on those fields a calendar widget (from here) pops up that allows the user to select a date range. However, when the user selects the date range and hits "apply" the form fields aren't updated.
EDIT
The form I'm using looks like this:
class DateRangeForm(forms.Form):
def __init__(self, *args, **kwargs):
initial_start_date = kwargs.pop('initial_start_date')
initial_end_date = kwargs.pop('initial_end_date')
required_val = kwargs.pop('required')
super(DateRangeForm,self).__init__(*args,**kwargs)
self.fields['start_date'].initial = initial_start_date
self.fields['start_date'].required = required_val
self.fields['end_date'].initial = initial_end_date
self.fields['end_date'].required = required_val
start_date = forms.DateField()
end_date = forms.DateField()
The view they are used in looks like this:
def table_search(request):
initial_start = "2015/2"
initial_end = "2015/222"
message = {'last_url':'table_search'}
if request.method == "POST":
daterange_form = DateRangeForm(request.POST,required=True,initial_start_date=initial_start,initial_end_date=initial_end)
else:
daterange_form = DateRangeForm(required=True,initial_start_date=initial_start,initial_end_date=initial_end)
search_dict.update({'daterange_form':daterange_form})
return render(request, 'InterfaceApp/table_search.html', search_dict)
The Django template here:
<div class="container">
<form action="/InterfaceApp/home/" method="post" class="form">
{% csrf_token %}
<div class="daterangepicker-container mcast-search-filter">
<div class="daterangepicker-label">Date range:</div>
<div id="daterange" class="daterangepicker-content">
{% bootstrap_form daterange_form %}
<i class="icon-calendar icon-large"></i>
</div>
</div>
</form>
</div>
<script>
// the start_date and end_date are the ids that django form fields created
$("#daterange").daterangepicker({
locale: {
format: 'YYYY-MM-DD'
},
startDate: '{{daterange_form.start_date.value}}',
endDate: '{{daterange_form.end_date.value}}'
});
</script>
EDIT 2
And the forms currently look like this (after #ShangWang suggestion) rendered:
Is there a way to display it so the start and end date fields show up? I tried changing the div class so it wasn't hidden, and then they showed up but seemed superfluous.
I use bootstrap-daterangepicker: https://github.com/dangrossman/bootstrap-daterangepicker. It would bind the widget's change to your django form field, so you don't need to manipulate the data once it comes to the views.py.
To get more details you should download and play with it, but here's a rough idea:
Your form.py:
class DateRangeForm(forms.Form):
start_date = forms.DateField()
end_date = forms.DateField()
def __init__(self, *args, **kwargs):
# initialize the start and end with some dates
Your template:
<div class="daterangepicker-container mcast-search-filter">
<div class="daterangepicker-label">Date range:</div>
<div id="daterange" class="daterangepicker-content">
<i class="icon-calendar icon-large"></i>
<span></span> <b class="caret"></b>
</div>
</div>
<!-- This is a hidden div that holds your form fields -->
<div class="hide">From {{ daterange_form.start_date }} to {{ daterange_form.end_date }}</div>
To trigger the widget you need a javascript binding:
// the id_start_date and id_end_date are the ids that django form fields created
$("#daterange").initDateRangePicker("#id_start_date", "#id_end_date");
I created a datepicker wrapper, and defined the initDateRangePicker function. You should put following code in a file called daterangepicker.js and import that in your template as well(or simply copy it into your template):
(function($) {
$.fn.initDateRangePicker = function(start_date_el, end_date_el, future) {
return this.each(function() {
var start = moment($(start_date_el).val());
var end = moment($(end_date_el).val());
var display_date = function(start, end) {
var str = ""
str += start.format('MMMM Do, YYYY');
str += " - ";
str += end.format('MMMM Do, YYYY');
return str;
};
$(this).find("span").html(display_date(start, end));
var self = this;
if(!future) {
$(this).daterangepicker({
format: 'YYYY-MM-DD',
timePicker: false,
ranges: {
'Last 7 days': [moment().subtract('days', 6), moment()],
'Month to date': [
moment().startOf('month'),
moment(),
],
'Last Month': [
moment().subtract('month', 1).startOf('month'),
moment().subtract('month', 1).endOf('month'),
]
},
}, function(start, end) {
$(start_date_el).val(start.format('YYYY-MM-DD'));
$(end_date_el).val(end.format('YYYY-MM-DD'));
$(self).find("span").html(display_date(start, end));
});
}
else {
$(this).daterangepicker({
format: 'YYYY-MM-DD',
timePicker: false,
ranges: {
'Next 7 days': [moment().add('days', 1), moment().add('days', 7)],
'Next month': [
moment().add('month', 1).startOf('month'),
moment().add('month', 1).endOf('month'),
],
},
}, function(start, end) {
$(start_date_el).val(start.format('YYYY-MM-DD'));
$(end_date_el).val(end.format('YYYY-MM-DD'));
$(self).find("span").html(display_date(start, end));
});
}
});
};
}).call(this, jQuery);
I try to display a custom list from my controller, but when I want to use the pagination,
it doesn't work: for example if I want to diplay 10 entries (params.max = Math.min(max ?: 10, 100)), in my gsp list view all entries are displayed in the same page. I also noticed that I have pagination but I when I use it, I still have all entries displayed.
My code
def user = User.findByLogin("John")
List MyList
switch (userView){
case "mylist":
params.sort="date"
params.order="desc"
params.max = Math.min(max ?: 10, 100)
MyList = DS.findAllByCpCreator(user,[params:params])
case ...
...
def DSList = MyList
def DSCount = MyList.size()
[DSInstanceList: DSList, DSInstanceTotal: DSCount,userView:userView]
In gsp view, I modified the pagination like this:
<div class="pagination">
<g:if test="${userView!=null}">
<g:paginate total="${DSInstanceTotal}" params="${[q:userView]}" />
</g:if>
<g:else>
<g:paginate total="${DSInstanceTotal}" />
</g:else>
</div>
In your action, you are passing findAll* a map of maps, it should be:
MyList = DS.findAllByCpCreator(user, params)
EDIT: actually your view tag is ok
For count, you should use: http://grails.org/doc/2.2.x/ref/Domain%20Classes/countBy.html
DSCount = DS.countByCpCreator(user)
This is roughly what I'm trying to do:
def post(request):
VehicleFormSet = formset_factory(StaffVehicleForm)
if request.method == 'POST':
vehicle_formset = VehicleFormSet(request.POST)
if 'add_vehicle' in request.POST:
if vehicle_formset.is_valid():
form_count = vehicle_formset.total_form_count()
vehicle_formset.forms.append(vehicle_formset._construct_form(form_count))
Basically, if a user clicks the "Add" button and their entry is valid, I want to add another blank form to the formset, and hide the previous one.
The problem with the code above is that I can't figure out how to increase total_form_count(). The way I have it now, it will work once, and then if you press it again, nothing will happen, presumably because form_count is the same. I also don't like calling _construct_form and relying on the internals.
class RequiredFormSet(BaseFormSet):
def add_form(self, **kwargs):
# add the form
tfc = self.total_form_count()
self.forms.append(self._construct_form(tfc, **kwargs))
self.forms[tfc].is_bound = False
# make data mutable
self.data = self.data.copy()
# increase hidden form counts
total_count_name = '%s-%s' % (self.management_form.prefix, TOTAL_FORM_COUNT)
initial_count_name = '%s-%s' % (self.management_form.prefix, INITIAL_FORM_COUNT)
self.data[total_count_name] = self.management_form.cleaned_data[TOTAL_FORM_COUNT] + 1
self.data[initial_count_name] = self.management_form.cleaned_data[INITIAL_FORM_COUNT] + 1
def add_fields(self, form, index):
super(RequiredFormSet, self).add_fields(form, index)
form.empty_permitted = False
That will do it. Only took 7 hours to figure out. And I still don't know why I need .is_bound = False to make the initial values not screw up.
I do this using javascript. Since the formset renders three management fields
<input type="hidden" id="id_TOTAL_FORMS" value="1" name="TOTAL_FORMS">
<input type="hidden" id="id_INITIAL_FORMS" value="1" name="INITIAL_FORMS">.
<input type="hidden" id="id_MAX_NUM_FORMS" name="MAX_NUM_FORMS">
you can use javascript to increment the id_TOTAL_FORMS value, and just add in the extra fields. So I'd create my fieldset like this:
VehicleFormSet = modelformset_factory(StaffVehicleForm, extra = 0, max_num = None)
The tricky thing is to create the extra form fields in javascript. I usually use AJAX to fetch a new row from a custom view.
For posterity here is another way which works without JS (or alongside JS) and which does not require intimate knowledge of formset methods. Instead, you can just inspect the POST data and adjust it as if JS had done some work client-side. The following makes sure that there is always (at least) one empty form at the end of the formset:
def hsview( request):
HS_formset = formset_factory( HSTestForm, extra=3 )
prefix='XYZZY'
testinpost, empty = 'key', '' # field in the form and its default/empty value
extra=3
# I prefer to do the short init of unbound forms first, so I invert the usual test ...
if request.method != 'POST':
formset = HS_formset( prefix=prefix)
else:
# process POSTed forms data.
# pull all relevant things out of POST data, because POST itself is not mutable
# (it doesn't matter if prefix allows in extraneous items)
data = { k:v for k,v in request.POST.items() if k.startswith(prefix) }
#if there are no spare empty forms, tell it we want another form, in place of or extra to client-side JS
#don't want to crash if unvalidated POST data is nbg so catch all ...
try:
n = int( data[ prefix + '-TOTAL_FORMS'])
test = '{}-{}-{}'.format(prefix, n-1, testinpost)
#print(test)
test = data.get( test, empty)
except Exception:
test = 'bleagh'
# log the error if it matters enough ...
if test != empty:
data[ prefix + '-TOTAL_FORMS'] = n + 1
# now the usual formset processing ...
formset = HS_formset( data, prefix=prefix)
# other_form = OtherForm( request.POST)
if formset.is_valid():
...
I use RegEx in my Vue.js method:
addForm: function () {
this.count++
let form_count = this.count
form_count++
let formID = 'id_form-' + this.count
incremented_form = this.vue_form.replace(/form-\d/g, 'form-' + this.count)
this.formList.push(incremented_form)
this.$nextTick(() => {
let total_forms = document.getElementsByName('form-TOTAL_FORMS').forEach
(function (ele, idx) {
ele.value = form_count
})
})
},
delForm: function () {
if (this.count != 0) {
this.count--
let form_count = this.count
form_count++
let formID = 'id_form-' + this.count
this.formList.pop()
this.$nextTick(() => {
let total_forms = document.getElementsByName('form-TOTAL_FORMS').forEach
(function (ele, idx) {
ele.value = form_count
})
})
}
else return
},