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
Related
I need some help in understanding MPMediaQuery and how to access the results so that I can use the query with setQueue(with:).
Here's an example of why I am confused.
In my library I have an artist with 3 albums.
My goal is a query for those 3 albums, in order with each track in order:
Album A
track 1
track 2
track 3
Album 2
track 1
track 2
track 3
Album 3
track 1
track 2
track 3
When I use this query, the Albums/songs are not in order as expected.
They almost appear shuffled even though shuffle is NOT on.
var qryArtists = MPMediaQuery()
qryArtists = MPMediaQuery.artists()
qryArtists.groupingType = MPMediaGrouping.albumArtist
let currLoc = qryArtists.collectionSections![indexPath.section].range.location
myMP.setQueue(with: qryArtists.collections![indexPath.row + currLoc])
for junk in qryArtists.collections![indexPath.row + currLoc].items{
print(" collections title \(junk.albumTitle!) track \(junk.albumTrackNumber) song \(junk.title!)")
}
I get these results:
collections title Cosmic Thing track 8 song Channel Z
collections title Cosmic Thing track 1 song Cosmic Thing
collections title Wild Planet track 6 song Devil In My Car
collections title Wild Planet track 2 song Dirty Back Road
collections title Wild Planet track 4 song Give Me Back My Man
collections title Cosmic Thing track 5 song June Bug
collections title Wild Planet track 1 song Party Out Of Bounds
collections title Wild Planet track 5 song Private Idaho
collections title Wild Planet track 7 song Quiche Lorraine
collections title Cosmic Thing track 6 song Roam
collections title The B-52's track 15 song Rock Lobster
collections title Wild Planet track 3 song Runnin' Around
collections title Wild Planet track 8 song Strobe Light
collections title Cosmic Thing track 9 song Topaz
collections title Wild Planet track 9 song 53 Miles West Of Venus
Notice the Albums/Songs are not in proper order
However, if I use this print statement instead I get expected results:
for junk in newQry.items!{
print("items title \(junk.albumTitle!) track \(junk.albumTrackNumber) song \(junk.title!)")
}
Results:
items title The B-52's track 15 song Rock Lobster
items title Cosmic Thing track 1 song Cosmic Thing
items title Cosmic Thing track 5 song June Bug
items title Cosmic Thing track 6 song Roam
items title Cosmic Thing track 8 song Channel Z
items title Cosmic Thing track 9 song Topaz
items title Wild Planet track 1 song Party Out Of Bounds
items title Wild Planet track 2 song Dirty Back Road
items title Wild Planet track 3 song Runnin' Around
items title Wild Planet track 4 song Give Me Back My Man
items title Wild Planet track 5 song Private Idaho
items title Wild Planet track 6 song Devil In My Car
items title Wild Planet track 7 song Quiche Lorraine
items title Wild Planet track 8 song Strobe Light
items title Wild Planet track 9 song 53 Miles West Of Venus
Also, another very strange effect: If I set the MusicPlayer query:
myMP.setQueue(with: newQry)
and then issue the SAME 'items' print statement, the results are now mixed in the exact same way as the 'collections' version!
Why would setting the queue change the way the query behaves?
Since I can't setQueue with newQry.items, how can I build a queue to get the Albums and Songs in expected order?
OK, I solved this myself with a lot more research.
The trick here is to use the ITEMS which are sorted correctly, and build a new Collection to use as the queue.
All it takes is that addition of a single line of code:
let collection = MPMediaItemCollection(items: newQry.items!)
and then a change to the setQueue function:
myMP.setQueue(with: collection)
Here's the final working code block - compare to my original post OP above:
let newQry = MPMediaQuery.albums()
newQry.addFilterPredicate(
MPMediaPropertyPredicate(
value: artistArray[indexPath.row + currLoc],
forProperty: MPMediaItemPropertyAlbumArtist,
comparisonType: .equalTo
)
)
//build a new collection with the sorted items then load the collection!
let collection = MPMediaItemCollection(items: newQry.items!)
myMP.stop()
myMP.setQueue(with: collection)
myMP.play()
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>]
I am trying to extract some attributes from this webpage.
url='http://m.search.allheart.com/?q=stethoscope'
I wrote the following xpaths for this -:
XPATH,ATTRIBUTE='XPATH','ATTRIBUTE'
NUM_RESULTS='NUM_RESULTS'
URL='URL'
TITLE='TITLE'
PROD_ID='PROD_ID'
IS_SALE='IS_SALE'
CURRENCY='CURRENCY'
REGULAR_PRICE='REGULAR_PRICE'
SALE_PRICE='SALE_PRICE'
conf_key={
NUM_RESULTS : {XPATH :'//div[#id="sort-page"]//div[#id="options" and #class="narrowed"]//hgroup[#id="sort-info" and #class="clearfix"]/h2', ATTRIBUTE:''} ,
URL : {XPATH:'//span[#class="info"]//span[#class="swatches clearfix product-colors"]//span[#class="price"]',ATTRIBUTE:'href'} ,
TITLE : {XPATH:'//div[#id="sort-results"]//li[#class="item product-box"]//span[#class="info"]//span[#class="title"]',ATTRIBUTE:''} ,
PROD_ID : {XPATH:'//div[#id="sort-results"]//li[#class="item product-box"]//span[#class="info"]//span[#class="swatches clearfix product-colors"]',ATTRIBUTE:'id'} ,
IS_SALE : {XPATH :'//div[#id="sort-results"]//li[#class="item product-box sale"]', ATTRIBUTE:''} ,
REGULAR_PRICE : {XPATH :'//div[#id="sort-results"]//li[#class="item product-box"]//span[#class="info"]//span[#class="price"]' , ATTRIBUTE:''} ,
SALE_PRICE : {XPATH :'//div[#id="sort-results"]//li[#class="item product-box sale"]//span[#class="info"]//span[#class="price"]' , ATTRIBUTE: '' } ,
}
chromedriver = "/usr/local/CHROMEDRIVER"
desired_capabilities=DesiredCapabilities.CHROME
os.environ["webdriver.chrome.driver"] = chromedriver
driver = webdriver.Chrome(chromedriver,desired_capabilities=desired_capabilities)
driver.get(url)
The idea is to extract the attributes from the 1st search page to get the name , url, title, regular price & sale price.
Skipping the rest of the code.. and later extract the text through a for loop.
When I try to get the items on sale,
driver.find_elements_by_xpath(conf_key[SALE_PRICE][XPATH])
driver.find_elements_by_xpath(conf_key[REGULAR_PRICE][XPATH])
However this gives me , regular_price,sale_price,is_sale as
['$5.98', '$5.98', '$24.98', '$3.98', '$6.98', '$13.98', '$24.98', '$19.98', '$18.98', '$3.98', '$5.98', '$24.98', '$12.98', '$24.98'] ['$49.99', '$96.99'] [1, 1]
while I would like -:
['$5.98', '$5.98', '$24.98','$49.99', '$3.98', '$6.98', '$13.98', '$24.98', '$19.98', '$18.98', '$3.98', '$5.98', '$96.99', '$24.98', '$12.98', '$24.98']
['','', '24.98', '' , '' ....]
[0, 0, 1, 0 , 0 ...]
Question -:
I would like to force the driver to return '' (or any placeholder) , so that I can have the signal that the product was not on sale.
The webpage will either have the class - : "item product-box" , or "item product-box-sale "
Also, I do not want to hard code this, since I need to repeat this logic for a set of web-pages. How can I do this better without looping through li[0], li[1] .. and so on.
Is there any method that exists to signal that class was not present when scanned in order ?
Using the Xpath's defined above, I do get the rest of the container correctly as -:
SEARCH_PAGE
244 Items ['ah426010', 'ahdst0100', 'ahdst0500blk', 'ahd000090', 'ahdst0600', 'pms1125', 'ahdst0400bke', 'ahdst0400blk', 'adc609', 'ma10448', 'ma10428', 'pm121', 'pm108', 'pm122'] ['allheart Discount Dual Head Stethoscope', 'allheart Discount Single Head Stethoscope', 'allheart Cardiology Stethoscope', 'allheart Disposable Stethoscope', 'allheart Discount Pediatric / Infant Stethoscope With Interchangeable Heads Stethoscope', 'Prestige Medical Ultra-Sensitive Dualhead Latex Free Stethoscope', 'allheart Smoke Black Edition Clinical Stainless Steel Stethoscope', 'allheart Clinical Stainless Steel Stethoscope', 'ADC Adscope-Lite 609 Lightweight Double-Sided Stethoscope', 'Mabis Dispos-A-Scope Nurse Stethoscope', 'Mabis Spectrum Nurse Stethoscope', 'Prestige Medical Clinical Lite Stethoscope', 'Prestige Medical Dual Head Stethoscope', 'Prestige Medical Sprague Rappaport Stethoscope']
And I need to get the lists of same length, corresponding to each of these, for Regular & sale price(and is_sale flag)
find_elements_by_X return a list of WebElements which each of them may call find_elements_by_X.
Use find_elements_by_X to get a list of all the products within the page.
Iterate through them all
use find_elements_by_X (on the current product) to get specific element like cur_price or is_on_sale.
Don't forget to initialize a default value.
Store the informations in a structure (map, class, tuple). Note it is easy to specify a default value in a class using __ init __()
I find css selector easier to read than xpath IMO. Try it using google chrome console (F12) + right click + copy CSS path. https://selenium-python.readthedocs.org/locating-elements.html#locating-elements-by-css-selectors
def nextItem(self):
active = self.skill_list_listbox.get(tk.ACTIVE)
listbox_contents = self.skill_list_listbox.get(0, tk.END)
current_pos = listbox_contents.index(active)
if current_pos + 1 < len(listbox_contents):
new_pos = current_pos + 1
self.skill_list_listbox.activate(new_pos)
self.skill_list_listbox.selection_set(tk.ACTIVE)
From what I can see within documentation this should highlight and activate the next item in the listbox. If I omit the selection_set I get what I'm looking for but there's no indicator of what's active. Adding it highlights an item, but if you continue to click the "next" button it simply adds to the highlight instead of just highlighting one item creating a long section of highlighted items, which I don't want. I've tried several different methods and this has got me the closest. If there was a 'clear selection' method I suppose I could get my desired effect of just having the next item selected and highlighted, but 3 calls just to do that seems a bit much for a common task? Any thoughts, or suggestions?
Below is an example of what I think you are trying to accomplish, using a button to select the next item in a Listbox. The gist of it is in the button's callback function, which calls selection_clear then selection_set.
Updated the example, hopefully a bit clearer as to what it happening
import Tkinter
class Application(Tkinter.Frame):
def __init__(self, master):
Tkinter.Frame.__init__(self, master)
self.master.minsize(width=256, height=256)
self.master.config()
self.pack()
self.main_frame = Tkinter.Frame()
self.some_list = [
'One',
'Two',
'Three',
'Four'
]
self.some_listbox = Tkinter.Listbox(self.main_frame)
self.some_listbox.pack(fill='both', expand=True)
self.main_frame.pack(fill='both', expand=True)
# insert our items into the list box
for i, item in enumerate(self.some_list):
self.some_listbox.insert(i, item)
# add a button to select the next item
self.some_button = Tkinter.Button(
self.main_frame, text="Next", command=self.next_selection)
self.some_button.pack(side='top')
# not really necessary, just make things look nice and centered
self.main_frame.place(in_=self.master, anchor='c', relx=.5, rely=.5)
def next_selection(self):
selection_indices = self.some_listbox.curselection()
# default next selection is the beginning
next_selection = 0
# make sure at least one item is selected
if len(selection_indices) > 0:
# Get the last selection, remember they are strings for some reason
# so convert to int
last_selection = int(selection_indices[-1])
# clear current selections
self.some_listbox.selection_clear(selection_indices)
# Make sure we're not at the last item
if last_selection < self.some_listbox.size() - 1:
next_selection = last_selection + 1
self.some_listbox.activate(next_selection)
self.some_listbox.selection_set(next_selection)
root = Tkinter.Tk()
app = Application(root)
app.mainloop()
I am building the GUI for a boardgame for my software engineering class. I am using the TKinter toolkit on Python 2.7 (windows). I am stuck right now because I cant seem to find a way to ignore/forget a certain ordering of buttons. Essentially, I am trying to create a grid of buttons that would represent my game board. And right now, I have a game board that has a total of 49 buttons on a 7x7 grid.
So far this is what I have been able to do:
Instantiate all my button objects where columns = x and rows = y. This easily build a grid of x*y
I then place each button into a list (lets call this list1)
I want to use my list of button objects to ignore/forget/delete (for lack of a better description) certain buttons. I am thinking I can create a 2nd list (list2) of the indexes of the button objects that I want to use grid_forget on and then compare my two lists and only keep the ones that are not in list2. Unfortunately, this doesnt work out the way I want it to. Here is the code:
gameboard = ttk.Labelframe(root, padding = (8,8,8,8), text = "Gameboard",
relief = "sunken")
#forgetButtons will not be displayed on the game board b/c they do not have a
#label (they are not a: room, hallway, starting space)
forgetButtons = [0,1,3,5,6,7,13,14,16,18,21,30,32,41,42,43,45,46,47,48]
#this list tracks all the buttons on the gameboard
myButtons=[]
count = 0
for x in range(7): #build a 7x7 grid of buttons (49 buttons total)
for y in range(7):
btn = Button(gameboard, width=7, height=4)
myButtons.append(btn)
btn.grid(column=x, row=y, padx = 3, pady = 3)
#do some comparison here between the two lists
#to weed out the buttons found in forgetButtons
#**or maybe it should not be done here?**
btn.config(text="Room%d\none\ntwo\nfour\nfive" % x)
You don't need grid_forget these widgets if you just don't create them.
import itertools
import Tkinter as tk
root = tk.Tk()
forgetButtons = [0,1,3,5,6,7,13,14,16,18,21,30,32,41,42,43,45,46,47,48]
myButtons = []
for x, y in itertools.product(range(7), repeat=2):
if not x*7 + y in forgetButtons:
btn = tk.Button(root, width=7, height=4, text="Room%d\none\ntwo\nfour\nfive" % x)
btn.grid(column=x, row=y, padx=3, pady=3)
myButtons.append(btn)
root.mainloop()
I don't know the order to calculate the position for forgetButtons (usually the first index represents the row and the second one the column), but you can easily switch it.