I'm new to Django and get easily lost.
I have this app that have items. They are set as a list with parent-child relations.
Later I want to display tasks attached to items. But for now. I can't even figure out how to display the parent-childs.
This is my simplt model
class Item(models.Model):
item_title = models.Charfield()
item_parent = models.ForeignKey('self')
I want to display them as:
Item 1
- item 2
- item 3
-- item 4
-- item 5
Item 6
- item 7
-- item 8
--- item 9
I have tried with making a view that take Item.objects.all().order_by('item_parent')
And the template with a FOR - IN. But I don't know how to seperate to show first parent then child, and another child is that exist.
I just manage to list everthing in order by the item_parent. Which is not the same.
Appreciate some expert help to a beginner like me.
If performance is not an issue then a solution based on MaximeK's answer is the simplest. The performance is not great as you are recursively querying the database. For a very small amount of items this is OK.
A more efficient way that also can support an indefinite depth of children is to fetch all the items at once and then create a tree that you can then traverse to print the items in order. It is more code to write but it might be educational if not directly helpful with your problem.
First step: we generate a tree for each item that does not have a root (stored in roots). Side note: We can think of these trees as one big tree starting at a single root node that has all the items with no parents as children, but for simplicity we don't do that.
references = {}
roots = []
items = Item.objects.all()
for item in items:
# get or make a new node
if item.pk in references:
n = references[item.pk]
n.item = item
else:
n = Node(children=[], item=item)
references[item.pk] = n
if item.item_parent is None:
# if item is root (no parent)
roots.append(n)
else:
# if item has a parent
if item.item_parent_id in references:
# item parent already seen
parent_n = references[item.item_parent_id]
else:
# item not seen yet
parent_n = Node(children=[], item=None)
parent_n.children.append(n)
Second step: we traverse the tree depth-first
def dfs(root, level=0):
print("-"*level, root.item.item_title)
for node in root.children:
dfs(node, level+1)
for root in roots:
dfs(root)
This is just printing the item_title with - in front to denote the indentation level. I generated some random items and the output looks like this:
python mouse cat
- mouse monitor car
-- blue car cat
green machine computer
- monitor green yellow
yellow pen blue
- mouse cat yellow
yellow blue green
- cat monitor python
-- blue yellow python
-- machine green cat
--- monitor blue python
-- machine computer mouse
-- machine car blue
car pen yellow
I don't know how to do this in Django templates, but we can generate HTML that looks like this:
<ul>
<li>pen monitor cat
<ul>
<li>computer mouse machine</li>
<li>yellow python car
<ul>
<li>monitor python pen</li>
<li>mouse blue green</li>
<li>python blue cat</li>
</ul>
</li>
</ul>
</li>
<li>mouse computer cat</li>
<li>computer python car
<ul>
<li>pen green python
<ul>
<li>mouse computer machine</li>
</ul>
</li>
<li>machine yellow mouse</li>
</ul>
</li>
<li>yellow python monitor</li>
<li>car cat pen
<ul>
<li>pen machine blue
<ul>
<li>mouse computer machine</li>
</ul>
</li>
</ul>
</li>
</ul>
Depth-first traversal that generates the above HTML. I wrote it as a class to avoid global variables.
class TreeHtmlRender:
def __init__(self, roots):
self.roots = roots
def traverse(self):
self.html_result = "<ul>"
for root in self.roots:
self.dfs(root, 0)
self.html_result += "</ul>"
return self.html_result
def dfs(self, root, level=0):
self.html_result += ("<li>%s" % root.item.item_title)
if len(root.children) > 0:
self.html_result += "<ul>"
for node in root.children:
self.dfs(node, level+1)
self.html_result += "</ul>"
self.html_result += "</li>"
r = TreeHtmlRender(roots)
print(r.traverse())
To render on a webpage you can simply send the HTML to your template via a context and use the safe flag ({{ items_tree | html }}). You can pack all I said in this answer into a neat template tag that will render trees if you need or want to.
Note: A clear limitation of this approach is that it will not function properly if not all items are selected. If you select a subset of all your items and if it happens that you select child nodes and omit their parents, the child nodes will never be displayed.
You need to use :
item_parent__self_set
Its mean for each item_parent you have the childs list (_set if for query_set)
When you define a ForeignKey, you automatically get a reverse relation.
You can do something more simple :
class Item(models.Model):
item_title = models.Charfield()
item_parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
And you retrieve :
for item in Item.objects.filter(item_parent__isnull=True):
print item.item_title
for child in item.children.all():
print child.item_title
I'm pulling lists on webpages and to give them context, I'm also pulling the text immediately preceding them. Pulling the tag preceding the <ul> or <ol> tag seems to be the best way. So let's say I have this list:
I'd want to pull the bullet and word "Millennials". I use a BeautifulSoup function:
#pull <ul> tags
def pull_ul(tag):
return tag.name == 'ul' and tag.li and not tag.attrs and not tag.li.attrs and not tag.a
ul_tags = webpage.find_all(pull_ul)
#find text immediately preceding any <ul> tag and append to <ul> tag
ul_with_context = [str(ul.previous_sibling) + str(ul) for ul in ul_tags]
When I print ul_with_context, I get the following:
['\n<ul>\n<li>With immigration adding more numbers to its group than any other, the Millennial population is projected to peak in 2036 at 81.1 million. Thereafter the oldest Millennial will be at least 56 years of age and mortality is projected to outweigh net immigration. By 2050 there will be a projected 79.2 million Millennials.</li>\n</ul>']
As you can see, "Millennials" wasn't pulled. The page I'm pulling from is http://www.pewresearch.org/fact-tank/2016/04/25/millennials-overtake-baby-boomers/
Here's the section of code for the bullet:
The <p> and <ul> tags are siblings. Any idea why it's not pulling the tag with the word "Millennials" in it?
Previous_sibling will return elements or strings preceding the tag. In your case, it returns the string '\n'.
Instead, you could use the findPrevious method to get the node preceding what you selected:
doc = """
<h2>test</h2>
<ul>
<li>1</li>
<li>2</li>
</ul>
"""
soup = BeautifulSoup(doc, 'html.parser')
tags = soup.find_all('ul')
print [ul.findPrevious() for ul in tags]
print tags
will output :
[<h2>test</h2>]
[<ul><li>1</li><li>2</li></ul>]
We have a Rails 4 application .
What is the best way to schedule whenever task in rails in midnight usa with daylight saving ?
We need to send email at 11.58pm in night of a day's report .
We are using tzinfo gem
TZInfo::Timezone.get('America/Denver').local_to_utc(Time.parse('11:58pm')).strftime('%H:%M%p')
is not sending email at the time .
This works around the timezone issue, server is at UTC and users in another time zone (with daylight saving time). define a local action and use in cronjob.
schedule.rb
require "tzinfo"
def local(time)
TZInfo::Timezone.get('America/Denver').local_to_utc(Time.parse(time))
end
every :sunday, at: local("11:58 pm") do
#your email sending task
end
hope it will help you.
Rehan's answer was SO great! In my use case I ran into an issue where the time zone conversion also changed the day of the week the task would be scheduled for.
Perhaps there is an easier way but here is what I did.
The timezone conversion we needed would only advance the weekday.
If your use case requires the weekday to be retreated then you will need to edit this, but it should be an easy fix.
def local(time, est_weekday = nil)
days = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday]
local_time = Time.parse(time)
utc_time = TZInfo::Timezone.get('America/New_York').local_to_utc(local_time)
utc_time_formatted = utc_time.strftime("%I:%M %p")
if est_weekday && days.include?(est_weekday.downcase.to_sym)
#extract intended wday for ruby datetime and assign
weekday_index = days.index(est_weekday.downcase.to_sym)
#get placeholder wday from desired EST day/time
temp_est_weekday_index = local_time.wday
#get placeholder wday from adjusted UTC day/time
temp_utc_weekday_index = utc_time.wday
#has the conversion to UTC advanced the wday?
weekday_advances = temp_utc_weekday_index != temp_est_weekday_index
#adjust wday index if timezone conversion has advanced weekday
weekday_index += 1 if weekday_advances
weekday = days[weekday_index]
return {time: utc_time_formatted, day: weekday || nil }
else
return utc_time_formatted
end
end
Is there a way (Using Django filters or any other language) to find and slice certain parts of a dynamically generated string? I have tried the slice method and the truncate method
{{variable|slice:"130:-60"}} or
{{variable|truncatechars:255 }}
but neither of those methods work exactly right..... I am working on weather alerts (provided by the National Weather Service) and each alert comes with a unique ID on the front and (sometimes) on the back too.
The unique ID #'s and length vary between 60 and 130 characters and the ID at the end is longitude and latitude but it's only included about 1/2 the time.
So I am looking for / working on code to "sniff out" and remove the unique ID's and to only provide the text for the user to see.
What is the proper method to do this?
Here is an example of an alert:
INC077-437-75584393-/09584738.EGY/W.0027//KT.0215401321/ 1100 AM CDT WED MAY13 2015 THE FLOODING WILL CONTINUE FOR THE MISSISSIPPI RIVER NEAR ORLANDO FLORIDA. FROM THIS EVENING TO THE END OF TIME AT 600 AM WEDNESDAY THE STAGE WAS 30.5 FEET. FLOOD STAGE IS 30.6 FEET IMPACT BY TONIGHT AT 1000 PM SOME WATER BEGINS TO FILL SOME DITCHES. && LAT...LON 4125 5845 5458 6548 8964 5124 1234 8706 $$
and with code (where I call the variable) I want it to be:
1100 AM CDT WED MAY13 2015 THE FLOODING WILL CONTINUE FOR THE MISSISSIPPI RIVER NEAR ORLANDO FLORIDA. FROM THIS EVENING TO THE END OF TIME AT 600 AM WEDNESDAY THE STAGE WAS 30.5 FEET. FLOOD STAGE IS 30.6 FEET IMPACT BY TONIGHT AT 1000 PM SOME WATER BEGINS TO FILL SOME DITCHES.
but I can't cut or truncate because the length of every weather alert is different and each unique ID is a different # and a different length.
Any help is appreciated!
You can add a custom method to your model such as :
class Weather(models.Model):
alert = models.TextField()
#property
def get_id(self):
return self.alert.split('/')[-1]
And in your template :
<p>{{ weather.get_id }}</p>
You can also create a custom template filter :
from django import template
register = template.Library()
#register.filter(name='get_id')
def get_id(value):
return value.split('/')[-1]
And use it in your template this way :
<p>{{ weather|get_id }}</p>
I'm trying to get the birthdays in the upcoming 20 days, given the below Person model:
class Person(models.Model):
dob = models.DateField() # date of birth
There are similar questions on SO already (here and here), but these do not cover my use case, as I'm storing a date of birth instead of the next birthday or a timefield.
I've tried to do some things like the following:
from datetime import timedelta, date
today = date.today()
next_20_days = today+timedelta(days=20)
Person.objects.filter(dob__month=today.month, dob__day__range=[today.day, next_20_days.day])
... but I get FieldError: Unsupported lookup 'day' for DateField or join on the field not permitted.
When I do e.g. Person.objects.filter(dob__month=today.month, dob__day=next_20_days.day), I do get the results for exactly 20 days from now. So I potentially could go over each of the 20 days in a loop, but that seems rather ineffective.
Any idea on how to do this the proper way?
FYI, I ended up doing the following which works for me and which does not require raw SQL.
Any improvements would be welcomed :-)
# Get the upcoming birthdays in a list (which is ordered) for the amount of days specified
def get_upcoming_birthdays(person_list, days):
person_list= person_list.distinct() # ensure persons are only in the list once
today = date.today()
doblist = []
doblist.extend(list(person_list.filter(dob__month=today.month, dob__day=today.day)))
next_day = today + timedelta(days=1)
for day in range(0, days):
doblist.extend(list(person_list.filter(dob__month=next_day.month, dob__day=next_day.day, dod__isnull=True)))
next_day = next_day + timedelta(days=1)
return doblist
Caveat: I believe calendars and time is hard. As a result, I feel obligated to warn you that I haven't rigorously tested my proposal. But of course, I think it should work. :)
Unfortunately, I think you should abandon date objects as the additional complication of year data precludes easy selects. Rather, I propose storing the birthday as a MMDD string (comparison of strings works, as long as you format them consistently). You can then compute your next_20_days and convert that to a similar MMDD string, as well as today, then use them as values to compare against.
I have three edge cases you should definitely make sure work:
Normal month rollover. (e.g., June to July)
Leap days -- don't forget to check presence as well as absence of Feb 29.
Year boundary -- you'll need to either do two queries and union the results, or do an OR query using Q objects.
Edit: See also:
How to store birthdays without a year part?
SQL Select Upcoming Birthdays
mySQL SELECT upcoming birthdays
and so on. I just did a Google search for "stack overflow birthday select".
I have been struggling with the same issue for the past days. I think I assembled a pretty solid solution that should allow you easily to derive all the birthdays to come up for the next X days. This query runs against the database-table geburtstage (birthdays) with the following 4 fields: ID (set as primary key) vorname (firstname), nachname (lastname) and geburtstag (birthday). Just create the table, fill in some records and run the query below:
select * FROM (
select curdate() AS today, DAY(CURDATE()) AS d_T, MONTH(CURDATE()) AS m_T, DAY(geburtstag) AS d_G, MONTH(geburtstag) AS m_G, subdate(CURDATE(),-20) AS date_20, DAY(subdate(CURDATE(),-20)) AS d_20, MONTH(subdate(CURDATE(),-20)) AS m_20, vorname, nachname, geburtstag, (YEAR(CURRENT_TIMESTAMP) - YEAR(geburtstag) +1 - CASE WHEN MONTH(CURRENT_TIMESTAMP) < MONTH(geburtstag) THEN 1 WHEN MONTH(CURRENT_TIMESTAMP) > MONTH(geburtstag) THEN 0 WHEN DAY(CURRENT_TIMESTAMP) <= DAY(geburtstag) THEN 1 ELSE 0 END) AS age, datediff(DATE_FORMAT(geburtstag,concat('%',YEAR(CURDATE()),'-%m-%d')),NOW()) AS no_of_days FROM geburtstage
union
select curdate() AS today, DAY(CURDATE()) AS d_T, MONTH(CURDATE()) AS m_T, DAY(geburtstag) AS d_G, MONTH(geburtstag) AS m_G, subdate(CURDATE(),-20) AS date_20, DAY(subdate(CURDATE(),-20)) AS d_20, MONTH(subdate(CURDATE(),-20)) AS m_20, vorname, nachname, geburtstag, (YEAR(CURRENT_TIMESTAMP) - YEAR(geburtstag) +1 - CASE WHEN MONTH(CURRENT_TIMESTAMP) < MONTH(geburtstag) THEN 1 WHEN MONTH(CURRENT_TIMESTAMP) > MONTH(geburtstag) THEN 0 WHEN DAY(CURRENT_TIMESTAMP) <= DAY(geburtstag) THEN 1 ELSE 0 END) AS age, datediff(DATE_FORMAT(geburtstag,concat('%',(YEAR(CURDATE())+1),'-%m-%d')),NOW()) AS no_of_days FROM geburtstage) AS upcomingbirthday
WHERE no_of_days >=0 AND no_of_days <= 20 GROUP BY ID
ORDER BY (m_G, d_G) < (m_T, d_T), m_G, d_G, geburtstag desc, age