Creating and modifying pages¶
Creating a new page¶
To create a new page, we first create a session and retrieve a root page, which will then hold our new page:
import ultimate_notion as uno
ROOT_PAGE = 'Tests' # page with connected Ultimate Notion integration
notion = uno.Session.get_or_create()
root_page = notion.search_page(ROOT_PAGE).item()
Using root_page
we can simply create a new page with:
my_page = notion.create_page(parent=root_page, title='Autogenerated page')
Our new page is empty and we can check that with:
assert not my_page.children
# or
assert len(my_page.children) == 0
Adding content to a page¶
Our new page can now be populated with the typical Notion blocks from the blocks module. Let's create a heading 1 block and append it to our page with:
h1 = uno.Heading1('My new page')
my_page.append(h1)
We can also append several blocks at once:
my_page.append([
uno.Paragraph('Some text...'),
uno.Code(
'SELECT * FROM MY_TABLE',
language=uno.CodeLang.SQL,
caption='SQL Query'
),
uno.Quote(
'"You know, for a mathematician, he did not have enough imagination. '
'But he has become a poet and now he is fine." - D. Hilbert',
color=uno.Color.BLUE
),
uno.Callout('Ultimate Notion rocks!', color=uno.BGColor.PURPLE, icon=uno.Emoji('🚀')),
uno.Heading2('Showing off some more...'),
uno.BulletedItem('First bulleted item'),
uno.BulletedItem('Second bulleted item'),
uno.NumberedItem('First numbered item', color=uno.Color.RED),
uno.NumberedItem('Second numbered item', color=uno.Color.RED),
uno.ToDoItem('First checked Todo', checked=True),
uno.ToDoItem('Second open Todo'),
uno.ToggleItem('Toggle item'),
uno.Divider(),
uno.Heading3('Some more fancy stuff...'),
uno.TableOfContents(color=uno.Color.PINK),
uno.Breadcrumb(),
uno.Embed('https://www.youtube.com/watch?v=dQw4w9WgXcQ', caption='Rick'),
uno.Bookmark('https://www.youtube.com/watch?v=dQw4w9WgXcQ'),
uno.Equation(r'-1 = \exp(i \pi)'),
]
)
Let's say we have forgotten one block and want to add it at a specific position, i.e. after
another block:
new_paragraph = uno.Paragraph('Some more text, which was added afterwards...')
my_page.append(new_paragraph, after=my_page.children[2])
As the block reference new_paragraph
is automatically updated when appended to the page, we can also use it now as a reference to append a new block after it, e.g.:
another_paragraph = uno.Paragraph('Another paragraph...')
my_page.append(another_paragraph, after=new_paragraph)
Another possibility to insert a block after a certain other block is to use the insert_after
method of a block itself. Let's say we want to add a divider after the block another_paragraph
:
another_paragraph.insert_after(uno.Divider())
To delete a block from a page, we can just call .delete()
on it:
another_paragraph.delete()
In some cases, you might rather want to replace a block with another block. For this we can use the .replace
method of a block.
first_block = my_page.children[0]
first_block.replace(uno.Heading1('Heading of my new page!'))
Also note that you can even pass a sequence of new blocks to .insert_after
and .replace
.
Tip
Python's walrus operator, i.e. :=
, can be extremely useful when working with blocks that you want to reference later on. So instead of assigning new blocks to variables and passing them to page.append(...)
, you could just write:
page = notion.create_page(parent=root_page, title='Another autogenerated page')
page.append([
heading1 := uno.Heading1('My heading'),
paragraph := uno.Paragraph('My paragraph')
])
print(paragraph.block_url)
Note
For type hinting, use AnyBlock for a general block, e.g.
new_blocks: uno.AnyBlock = [uno.Heading1('New section'), uno.Divider(), uno.Paragraph('My paragraph')]
Modifying blocks¶
Most properties of a block also provide setter, so you can just assign a new value to the property to modify it. Let's assume, we first have to retrieve the page we created above and want to modify the first heading:
my_page = notion.get_page(my_page.id) # or `notion.search_page(...).item()` in practice
heading = my_page.children[0]
assert heading.color == uno.Color.DEFAULT
heading.color = uno.BGColor.RED
heading.rich_text = 'Red heading of my new page!'
Check the blocks module to see which properties can also be set. Ultimate Notion allows updating all properties that could also be udpated via the update-a-block endpoint of the official Notion API.
Our page now looks like this so far:
Bug
Sometimes setting certain attributes, for instance the color
property with a foreground color, i.e. uno.Color
, can result in the default black text although the property is set correctly when checked in the Notion app. This seems to be a bug within the Notion backend itself.
Nested blocks¶
A really useful feature of Notion blocks is that most of them can be nested Therefore some blocks, like paragaphs, quotes, toggable headings and so on, can have children
and thus also have an append
method, just like a page.
Let's create a new page where we store various file types under a toggable Headning:
file_page = notion.create_page(parent=root_page, title='Page with nested blocks')
heading = uno.Heading1('My files', toggleable=True)
file_page.append(heading)
heading.append([
uno.File(
'robots.txt',
'https://www.google.de/robots.txt',
caption='Google Robots'
),
uno.Image(
'https://cdn.pixabay.com/photo/2019/08/06/09/16/flowers-4387827_1280.jpg',
caption='Path on meadow'
),
uno.Video(
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
caption='Rick Roll'
),
uno.PDF(
'https://www.iktz-hd.de/fileadmin/user_upload/dummy.pdf',
caption='Dummy PDF'
),
])
Info
We can only add children blocks to a block that is already in notion, i.e. was already appended to a page. You can also programmatically check that with the in_notion property of a block.
Columns & tables¶
Assume that we want to structure our page a bit more using columns. We define a set of columns using the Columns block. This block now behaves like a list of where each element presents a column. We can now just append to a column, e.g.:
page = notion.create_page(parent=root_page, title='Page with advanced blocks')
cols = uno.Columns(2)
page.append(cols)
cols[0].append(uno.Paragraph('Column 1'))
cols[1].append(uno.Paragraph('Column 2'))
Using the some concept, we can also create a table but this time we specify the cells instead of columns. Creating a 4x6 table with 4 rows and 6 columns where the first row is a header row and thus we set header_row
to true. We can use indexing to assign either whole rows or specific cells:
table = uno.Table(4, 3, header_row=True, header_col=False)
table[0] = ('First name', 'Last name', 'Sports')
table[1, 0] = 'Florian'
table[1, 1] = 'Wilhelm'
table[1, 2] = 'Running and bicycling'
page.append(table)
Note
All cells of a table are initialized with an empty string. Therefore, to delete the content of a cell, you need to assign an empty string, i.e. ''
. For instance, to delete the first row after the header:
table[1] = ('', '', '')
To access the all rows, i.e. children of the table, we can use children
:
rows = table.children
assert isinstance(rows[0], uno.TableRow)
assert isinstance(rows[0], tuple)
In order to access a single row or even a cell, we can use indexing like:
row = table[0]
cell = table[0, 0]
assert cell == 'First name'
When using only a row index, a TableRow
object will be returned that is a subclass of tuple
with special functionality. For instance, to delete a row from a table you can call delete
, e.g.:
row.delete()
Tables can be easily modified in various other ways using the insert_row
, append_row
methods as well as assigning to the has_header_col
and has_header_row
properties.
Advanced blocks¶
There are some additional more advanced blocks like link to a page and synced block. Let's take a look at those.
Assume we have our "Getting Started"-page and want to link to it from the page, we just created above.
target_page = notion.search_page('Getting Started').item()
page.append(uno.LinkToPage(target_page))
A synced block is a different kind of beast. We first have to define an original block, which acts as a container for other blocks that should be synced. Then we append various blocks to it and finally create a synced block, which will be synchronized with the content of the original block.
orig_block = uno.SyncedBlock(uno.Paragraph('This is a synced paragraph'))
page.append(orig_block)
sync_block = orig_block.create_synced()
page.append(sync_block)
Now we have two paragraphs, with the second always reflecting the content of the first.
Working with texts¶
So far we passed to most blocks basic strings and only provided in some cases a colour for the whole block. Notion allows much fancier formatting for texts, i.e. rich texts, but also inline formulas and mentions of various types. All this is supported via the Text object, which is a subclass of the ordinary str
type. For your convience three different constructor functions, i.e. text, math and mention, are provided that construct a Text object. An example illustrates this:
page = notion.create_page(parent=root_page, title='Page with fancy text')
rt = uno.text('This is a ') + uno.text('bold', bold=True) + uno.text(' word.\n')
assert isinstance(rt, uno.Text)
rt += 'We can even add a normal string to it.\n'
rt += uno.text('Now some ') + uno.text('inline formula: ', color=uno.Color.RED)
rt += uno.math(r'-1 = e^{i \pi}') + '\n'
rt += uno.join([
uno.text('All', italic=True),
uno.text('possible', underline=True),
uno.text('formatting, ', color=uno.BGColor.PURPLE),
uno.text('is', code=True),
uno.text('it?', strikethrough=True),
uno.text('it not?', italic=True, bold=True)
])
page.append(uno.Paragraph(rt))
To achieve fancy formatting, we have to mix and match Text
objects generated by text, mention and math to compose the formatting we want.
Warning
Be cautious with the operator precedence of +=
and +
in the example above. While rt += 'We can even add a normal string to it.\n'
works since rt
is of type Text
, which supports this, rt += 'We can even add a normal string to ' + uno.text('it.\n')
would not work as we would first conatenate a str
with a Text
before we append it to a Text
. A str
object would ignore the additional formatting information in the Text
object while concatenating.
Let's see how we can mention a person, page, database or even a date.
from datetime import datetime
person = notion.search_user('Florian Wilhelm').item()
dummy_db = notion.create_db(parent=root_page)
dummy_db.title = 'Dummy DB'
intro_page = notion.search_page('Getting Started').item()
my_date = datetime(1592, 3, 14) # or better use the pendulum library
rt = uno.join([
uno.mention(person),
uno.mention(dummy_db),
uno.mention(intro_page),
uno.mention(my_date)
], delim=' : ')
page.append(uno.Paragraph(rt))
Let's take a look how our fancy text page looks like in the end.