Tip:
Highlight text to annotate it
X
Let's talk about the solution to the bonus problem now, which was add version history.
The way that works remember is if I click history, we can see the previous versions of this page.
If I were to make a new version of this page, I can click any of these edit links.
If I click save, we can see we edited the page.
If I click on history again, we can see this new version--version 3.
You can see the content of that page up here.
That was feature number one required for the bonus, which is view the history of all these pages.
Feature number two is view any particular version of this.
I can't just go to /new page. I have to also say what version it is.
If I were to go to this middle version here, I'm going to newpage,
and then I added a get parameter for what version it is. In this case it's 5.
That's just the database ID.
I think this one is version 10, because I've made other objects.
So Google is just assigning the IDs and it doesn't really matter.
Now, of course, I can edit any version of these.
They have their own special URL.
This is editing new page v4, which is that first version.
If I were to save this, it overwrites the newest version.
If we were to look at history again, we can see it's plopped up on the top.
If I want to just store the most recent version, I can just click edit, save,
and there is our page again, and if we go to history, we can see all the history.
That's pretty cool. Let's talk about the implementation.
The first thing is there are a number of ways you could've done this.
You could have one page that has, instead of content, it could have a list of contents.
There is a way to store lists of things in datastore. That's a fine way to do it.
What I did is I basically said every page has a name, which is always going to be pages,
and it has a parent called /root plus the path.
I put /root in there because I thought it was going to be more complicated than it was.
But basically remember entities in the datastore can have these ancestors--
this hierarchy of objects. Now, we didn't spend a whole lot of time talking about this.
Understanding that isn't required for doing this homework,
because there are many ways you could have done it.
This is how I chose to do it.
Every URL has basically this common ancestor.
So when we look up a page by path,
all I'm really doing is I'm saying get me all the pages that have this ancestor.
Basically give me all of the pages that belong to this URL sorted by most recent.
Then I always just use 'get' to get the most recent.
If I want to get an old version, I use the getbyid function,
which takes an ID, and it also takes a parent key,
which uses the same key as the main path query.
What this is saying is get me the page--in this case class refers to the page,
because it's the class method and we're in page--
get me the page by ID. This is built in the datastore.
I want this ID and this parent.
So we can go get older versions out of history.
Then we we're rendering this stuff, you can see here on EditPage
I can call by_path and then call get to get the first response out of the query,
or I can do this other thing, which is look for the v parameter, which is the version.
If it's there and if it's a digit--if it's basically valid--try to run by_id on it.
If that's not there, that page, that version just doesn't exist--Not Found.
Otherwise--basically if there's no v parameter--just look up the normal version.
We have the same block of code in the normal wiki page for viewing any page on the wiki.
This exact same block of code, and of course we'd want to clean this up.
The code that I'll give to you will probably have this cleaned up in it.
Let's take a look at some of these templates real quick so we can see
the last bit of how all this fits together.
I'm using template inheritance again.
My base template basically has the title of the wiki.
It references my style sheet, and it's got the basic structure of my document,
which is going to have a body tag and then a couple of divs.
These classes are just for styling--not required for completing this homework.
Just required to make your homework look a little bit better.
I want my stuff to look slightly good, so I use them.
This is the whole header area where we draw those links.
Basically, if there is a user, we print the user name and then we print the logout link,
which is just a basic link to /logout.
If the user is not logged in, we draw two links--one for login and one for signup.
Those are also just basic links.
They both have the gray link style, which is how I make them gray
and I make that underline hover thing work.
Then I have a div here that basically doesn't have any thing in it.
This is just a block. Remember this is kind of a way of doing this kind of templated inheritance stuff,
so you don't have to write the same HTML over and over.
Other templates that inherit from base will fill in this block.
This where we put in the controls for editing a page, viewing a page or viewing the history of a page.
All that stuff gets overwritten in the other templates.
This clear div technique--the technique I use to float those links over to the right is called "float."
When you float something it doesn't take up any space.
One of the techniques to make it take up space is use this clear div.
I'm not going to attempt to explain that here, but it's basically a CSS trick for
floating something over to the edge of the screen and still having it have vertical height.
Here we have the actual content.
This is where the edit window is going to go or the wiki content page.
And again there's no content in here, we just define the block and the other templates will override it.
Let's look at the template for page html which basically draws the actual wiki content page.
It extends base.html, which basically says use all that other stuff and then we can just override the two blocks.
On an edit page, or I mean sorry on a viewing page, a normal wiki page, you have two links.
One for edit and for viewing the history.
But we only draw the edit link if the user is logged in.
If the user is logged in, draw the edit link, regardless of whether the user is logged in, draw the history link.
And these two links are simple. One is _edit and the path, remember the path was passed into my render function.
And one is _history to the path.
Both those links are simple links that are handled by the appropriate handler .
Then, the actual content, we render the page content and I use the jinja safe notation which
basically says don't escape this, which basically says take the html that was typed in and insert directly in the page.
Security issues be damned, since I entered all the html, I know it's okay.
Now, if you're going to put this website online, you want to have probably some special escaping.
What a lot of websites do, is they only allow some html or they have a special language that turns into html
or they don't allow html at all but that wouldn't allow links and then the wiki wouldn't be very useful.
That's a design issue, that's up to you.
Edit html, this is our template for the edit page.
The controls are very similar to the viewing page.
We have a link to just called view which links to the page itself, basically kind of like a cancel button.
Then we have a link to the history of the page so you can view the history of the page right from the edit page.
And these are just basic links and we only draw these links if the page already exists.
If you're creating a new page, there's nothing to view and there's no history so that's what if page is done.
And remember when we call this template, we pass in whether the page existed.
Now here is my form, it's a method post, it has this class, it's called main edit, that's just for styling purposes
to make it render nice and big.
It's basically a giant text area whose name is "content" and who includes basically page and page.content.
So if the page exists, render its content, otherwise render an empty string, that's what going on there.
Then, at the bottom of the form, we have this little area called form footer which just has the safe button.
We'll go through all that CSS.
So the edit page is actually fairly simple as well.
The history page is for viewing the history of a wiki page.
Again, we've got our controls, if the user exists, draw the edit link, just like the main wiki view page.
Regardless of whether user is logged in, draw the view link to just view the page instead of viewing the history.
Then our content here, I'm drawing a table.
Now, we didn't spend a whole lot of time or any time at all on tables in this class so use some
of the examples that I've given you.
But it does what you would expect, it draws a table.
We draw a table, history table is what I used to style it.
Now, I will show you that function again called gray style.
What this does is basically for p and posts, return the p and basically whether that row is even or odd,
so that's how I do that effect where each row, one is white and one is gray.
So I will show you the effect first.
Here it is, this is the history page.
You can see the first row is white, second row gray, white, gray, white.
That's kind of a nice effect when you have a table.
I just did a simple version of that.
The code to generate which of these rows should be gray is something like this.
Here is that gray style function, we'll look at this in template again but basically it takes a list and it enumerates
over the list, this is a built in python function that basically says return the elements of list in a tuple along with
the index of that element in the list.
For x and list, it would return basically each page, page 1, page 2, page 3.
For n x in enumerate list basically says index into the list, 0, page 1, 0, page 2 as tuples.
Then if n is modulo 2 = 0 basically if it's even, the row should be blank or basically return x and blank.
If it's odd, return x and gray.
There's a lot of stuff going on in this function.
It's also using yield which is how you build generators.
This is complex stuff, not required for the homework at all but this is how I wanted to create that effect.
You can also create the effect in the template itself by having a variable that you create
and check to see if it's even or odd.
This is the more pythonic way of doing it.
Back to the template, p rowstyle so rowstyle is either going to be a blank string or the string gray.
Then I use that in the class name for each row.
Each row is either going to have a class of nothing or of the word gray and that's how in the CSS
I decide whether to make the row gray or not.
So then I have three columns, that's what TD does.
First column is the date, so I just print the last modified time.
The second column is the content, I print the first 100 characters of the content and then the
next column is a link to view that version of the page,
which is just a normal link, it's got that gray link style on it
which makes it look like my other links.
Then href points to the path and then I add a gip parameter v = and then the ID of that post.
Remember .key.ID is how you get the ID of an element in Google datastore or of an entity.
The url would be basically the path of the page we're on plus the ID stored in the v parameter.
We saw in the edit and the wiki handlers, we look for that v parameter to see if we can find a specific item.
Going a little bit further down, if the user is logged in, if user, we create the "edit" link.
This is a link to edit or it's a link called "edit" and it's a link to /_edit and then basically the same link as up here.
Path, v = and then we include the idea of the page.
Now, I should probably clean this up, I should probably have a function maybe on page that says "edit url", "history url",
"normal url", "version url", that sort stuff would be really handy and I would have to keep
including that in the template and that's probably one of the first changes I'll make because this is kind of error prone,
you've got all this code that's the same, these two lines are the same basically and you don't want to get into
that situation, because if I edit one I've got to remember to edit the other, etc., etc.
Then we just close out our if statement, our four loop and our table and that's how we do the history page.
So that's all the templates, I'll walk you to the CSS real quick, that I use to style things and then we'll be done.
First I say "html", remember outside the body element is the html element, I say height 100 percent and this is
part of what allows me to make that text area on the edit page take up the whole page.
Let's take a look at that real quick.
If I click edit up here, my technique for making this text area fill the entire browser is basically to make the html
which normally is just the size of the content, actually take up 100 percent of the space
so that's what that 100 percent does.
The body element also needs to be 100 percent for the same reason.
Position relative is when you need to adjust where things appear relative to where they should appear or
you need to have position relative on a parent element of an element that you're going to position absolutely which is
what you do when you want something to be positioned specifically on the page and we'll come down to that.
I choose my font, my default font size, the width of the whole document which is 800 pixels.
Margin 0 auto basically says center that.
Our default color which is this gray color for all our text.
The header area of the page is 30 pixels tall, the line height is 30 which causes the text to actually center vertically.
We get a 10 pixel margin for the content.
The control element floats to the right, control element, those are the links for editing, viewing and history
that appear over here in the browser.
Float right says take the content and float it to the right.
And it's gray, font size 13.
Controls following controls, give it a little space between them, remember we have separate controls for
logging in and sign-up and for doing the page actions, then just put some space between it.
I can show you that real quick.
Right here, this is that 20 pixels, between those two things, this is one control element and this is another
control element and they're both floated to the right so they kind of stack over there.
That's what those two things do.
A gray link , we use this all over the place, basically make our links gray and don't underline them.
If they're visited, instead of drawing them as purple, draw them as gray.
If the mouse is over them, that's what hover does, draw the underline, so that behavior will look something like this:
when I put the mouse over the log out here you can see the underline appears and all of our links have that effect
and that's kind of an effect I like.
You can see we used the gray link over here on all these styles as well so the underline appears when the mouse
goes over it and you can do that all in CSS.
Errors should be red, we use that all the time.
Main edit, this is the class that's on that main form and it's position absolute so that allows me to basically
force the positioning of this element so I say positioning at 45 pixels from the top of the page, 0 pixels from the right,
10 pixels from the bottom, 0 pixels from the left so that makes our form grow up to fill the whole space.
Then text area under main edit is the same thing except 0 pixels from the top of the form, 30 pixels
from the bottom of the form, that gives us room for the button and then 0 pixels from the left, 0 pixels from the right.
That makes our text area take up nearly the entire form.
Our form footer is also positioned absolute and that's at the bottom, 0 pixels from the bottom, 0 pixels from the left,
0 pixels from the right.
I didn't specify top which basically means draw it normally and I say height 30 pixels so that draws a row along
the bottom for a save button that is 30 pixels tall.
Our submit button is font size 18 pixels.
Here is our table, border collapse, collapse basically just shrinks our table a little bit, otherwise tables have this
default spacing between all of the cells.
If a table has a CSS attribute of gray, which is how we do alternating rows, set the background color
to "eee" which is basically light gray.
If it's a date cell, make the width 200 pixels.
If it's a content cell give it some padding so it fattens up our table a little bit.
If it's a link cell have a little--padding, just giving it a number says put padding on top, bottom, left and right 10 pixels.
If you do it like this, the first number is vertical padding which we say zero and this is
horizontal padding five pixels and this gives us this effect.
The little five pixels between the view and edit here, if I got rid of that these would actually--
there would be no space between these words because we're in a table so that just adds that little space.
That's it, I know it's a lot.
We didn't cover CSS in this course but I'd still like to show you it in case you have questions.
I'll provide the CSS and you can play around with it in your own projects and that's it for the final.
If you managed to figure all that out, that's really impressive.
I know that the final stuff was challenging and the bonus question of doing the history itself was also pretty tricky.
So if you managed to get all of this working that's a very good job, that's really cool and that's a sign that you have
learned a lot in this class and for those of you who didn't figure it out or didn't try that's something
to aspire to down the road.
So that's it for the class, I want to thank you all for
hanging with me this far, it's been a really fun experience
and I hope you learned as much as I did.
Over the last few weeks, it's been a lot of fun.
I'll see you around the internet and good luck out there!