This is the final exam homework solution.
Let's talk about the basic version first without the bonus problem.
The basic version was to have a basic wiki.
This is the front page. Of course there are other pages you can go to.
If they exist, you see the content.
If they don't exist, for example /foo, you would see an edit page.
But I wasn't logged in so we won't see an edit page.
Let's try that again.
If you try to visit an edit page when you're not logged in,
it should redirect you to the login page.
So I go to a page that doesn't exist--foo. Now I'm logged in.
We can see I put the user name in the upper right here.
Then we get an edit page, and if we save this page, you can see we can now
go to /foo and we can see this content.
We go back to our front page and all is well>
We've got logging in and logging out. All that works.
Let's take a peak at the code.
Now, this actually wasn't a whole lot of code to get a pretty functional wiki.
A lot of this code you've already seen before.
Oh, there's my secret. I guess I'll change that before I upload this.
All of this code is basically from previous homeworks.
I've actually given you most of this code.
Here's my normal render
and check
I added this new function called gray_style, and we'll come back to this for
the bonus question but this is just a little function I wrote
to alternate the coloring of rows in the UI.
I don't know if there's a better way to do this or not, but this is how I did it,
using something that would work in Jinja, the templating language,
that I could figure out how to do otherwise.
All of these functions--remember this is our superclass Handler
that we've been using for all of our handlers.
None of this stuff has changed.
I've added it to my render_str function, I make it sow in templates
so I always have access to a variable called user, which just references self.user.
I have access to the function gray_style, which is that function I just pointed out.
We'll come back to that.
Everything else in here is actually the same.
These are all basically how I left it after the end of Unit 4, really.
You can see there is still some security stuff in there.
I had made it so only spez could submit to the blog,
and this variable isn't used in this program anymore. It's just leftover.
I made a new 404 page so I could call self.notFound,
and it'll actually say 404. That was something that I found kind of useful.
We've got all the solved and valid password and hashing functions.
This was all, again from really Unit 4 homework.
The user code hasn't changed at all.
The same stuff I demoed there.
The SignupHandler is just about identical from Unit 2 and Unit 4 where we did the registration.
The same form and the same validation. You've seen all this code before.
The login is all basically the same.
The only real change--actually, let's go back up to signup.
I'm using the refer to keep track of what URL the form should go to after you submit it.
Before we render the form, I create this variable next_url,
which uses the refer header, which is sent along with every request.
That's the page you are on before you went to this page.
If I was on '/', and I clicked on login to go to /login,
the refer would be '/', and then of course I have it default to '/'.
That gets sent in to the form as an input type hidden,
and the URL to go to next after we're done signing in or logging in
gets passed in the form, and you can see that document here.
This is the signup handler.
In the post for that I look for next_url in the requests, and if it's there
and if it's not login, because I don't want to have somebody signup and then immediately
redirect them to /login. That came up during testing.
If that parameter wasn't there or it's /login we just go to '/'.
Otherwise, next_url we've validated everything
and we redirect the user to next_url when they're done registering.
That allows somebody to click signup on any page,
and then once they sign up we take them back to that page.
It's just an extra kind of nice little convenience.
The login handler is almost identical to Unit 4 as well except it has the same
next_url functionality.
We get the referer header. I send that to my login form. I include it as an input type hidden.
Then we redirect to it when the user logs in successfully.
The rest of the code is the same from the Unit 4 homework.
Login is basically the same except it also has a next_url.
It just again looks at the refer and redirects the user to that.
Now, I should probably clean this up a little bit.
We've seen this exact line--this next
It'd probably pull that up into the handler, but this code is kind of a work-in-progress.
You're seeing it here in a little bit of a raw state.
This is my page class. The page model is pretty simple.
It just has content, a created time, and a last_modified time.
I also wanted to have a notion of an author of what user created a particular page,
but it actually wasn't relevant to the demo, so didn't bother finishing the implementation,
but it would have looked something like this.
You can see, again, this code is kind of in a working state,
and actually what is online right now might differ from what I'm explaining here,
because I've had a few more weeks to work on it to make it a little prettier.
You may see this feature implemented when I release the code,
or you may see this line be gone altogether.
Now, I've added a couple of class methods.
Some of you actually asked about this in some of the previous homeworks--
what staticmethod and classmethod do.
Staticmethod basically means you don't have to pass in self to a function,
which means this method here is just static.
It doesn't run on an instance of the class.
It's just basically a way of organizing your code a little bit better.
This function parent key is only going to be called in the context of a page,
so I just basically say this parent key function belongs to the page
and that's how we're going to reference it in our code,
but it doesn't actually take an instance of the class self
or the class itself like class.
It operates kind of independent.
What this function is actually doing is to make the history function work.
This is a little trickery there.
I'll come back and explain this in a little bit more detail when I explain
the solution to the bonus problem.
Then I added two class methods.
One is by_path for looking up a page by URL, which is kind of the default way of looking something up.
All this does is this runs an app engine query or datastore query.
This is a class method and what class method does is it says instead of running by_path
on and instance of page, run it on page itself--just the definition.
You can say Page.by_path, give it a path, and it will return a query object.
Basically what this is say is get me all the pages for this URL sorted by creation time.
That's kind of how I did versioning.
I'll come back to that in more detail again when I talk about the bonus problem.
Of course looking up Page.by_id--this is strictly for the bonus problem--
looking up older versions of a page, so if you want to look it up by id
in addition to the path, that's what this function is for.
NoSlash is a special handler for getting rid of extra slashes in the URL.
It was kind of a work-in-progress. It wasn't working in this demo.
I'm going to have to come back and clean that up. Just ignore that for now.
Here is the EditPage handler. EditPage is the handler for actually editing one of the wikI pages.
First thing we do is we check to see if the user is logged in.
If the user is not logged in, we redirect to /login.
That's important because you don't want non-logged in users to be editing your pages.
They should be registered, so you know who's doing what.
This code here is for the bonus problem.
If you didn't do the bonus problem, all you need to really do is look up a page.
This is that by_path function that I just explained on the path object.
We give it the path, and then we get the page object.
You can structure this however you like.
What that by_path function returns is a query and on a query datastore
you can either call get to get the first element of the query or you can call fetch to get multiple ones.
I'm calling get, because I only want the first one.
Then we render edit.html with that page, and we include that path
so I can render some of the links that appear in the navigation section.
Here's the definition of a post for the edit page.
Again we check to see if the user exists.
Now you may say, well, if the user didn't exist, how did they view the edit page?
Well, they could have a couple tabs open or multiple browser windows open.
If they log out on and then submit a form in the other that could cause some problems.
What we do is if the user is not logged in we just return a error 400,
which is basically a bad request, and return.
I didn't do anything user friendly here because that shouldn't happen.
It just kind of silently fails.
Then what we do is we look for the actual content in the request.
Then we look up an old version of the page.
Then we have this little thing here.
Basically, if there wasn't an old version of the page, you're creating a new page,
and there wasn't any content--just return.
I'll probably expand this to have an error message,
but we left out a little undefined in the problem.
I hadn't implemented it right away and so right now we're just kind of ignoring that.
Otherwise, if we either don't have a page and have content, which is what this clause implies,
or we do have a page and the content has changed.
I make a new instance of page.
Now, depending on whether or not you did the bonus problem,
you could have edited an old version of the page.
In mine, I want to keep this version history, so I create a new page for every change.
Then we store that in the database.
Then we redirect to just the path without the _edit,
so you can actually view the content.
I'll skip the history page handler, but it's actually pretty simple.
We'll come back to that in a second.
WikiPage is the handler for actually viewing any pages on the Wiki.
It takes a path argument just like the edit page does.
Notice it's only get. There's no post here.
This code here is identical to the edit page, and so again, I'll probably clean this up.
This basically decides whether you're looking at an old version of Page with the version number
or the most recent version of the page.
If you didn't do the bonus problem all you need is something like this.
This says just look up the most recent version on the page.
If it exists, we render page.html with that page.
I also include the path so I can render those links.
If it doesn't exist, I redirect this /_edit plus the path.
So how do I do all the URL handling? It's actually pretty simple.
What I did is I created a regular expression to match what I think is a valid URL>
Basically, it has to start with "/" and then it contains one or more
of the following characters--little a through little z, big A through big Z, 0 through 9,
underscore, and dash--followed by an optional slash
and then as many of those clauses as you want.
That basically means slash is valid and then any number of these clauses
is valid after that. Then our URL handlers look something like this.
So /signup goes to Signup handler, /login Login, /logout Logout.
These are all handlers you've seen from Unit 4.
This was my NoSlash thing that is currently not working so I commented it out.
Now, remember these URL get matched in order.
You want to make sure that this handler here or this regular expression
for rendering a wiki page would actually also capture the history URL and the edit URL,
so we need to put those history URL and the edit URL first so they get matched first.
What this one says here is it says if the URL is of the form /_history
plus this regular expression--basically /_history/anything--
match the history page.
If the URL is /_edit, basically anything, go to the edit page,
and if it's anything else, go to the wiki page.
That's how we go to one of those three handlers.
If we take a look at the app again in the browser now that we have seen the code,
some things will make a little bit more sense.
When you just go to "/" we go through our URL matcher,
and we say, okay, it doesn't match login, signup, or logout.
It doesn't start with
Our path now becomes "/" and then we look up using our database query the page for "/".
We get this page.
If I were to go /newpage, we do the same thing.
We use this as the key into our database, and we look up the most recent version of newpage.
Here it is.
Now, let's talk about editing a page.
I'm not logged in, so I don't have my edit link.
Let's go ahead and log in, so we can see the edit link.
Here I am on newpage. Now I'm logged in as spez. I have my edit link.
I'll show you those templates in a moment.
When I click on edit, you can actually see it basically links to _edit/newpage.
It basically inserts an _edit here.
Remember that goes through our edit handler instead of the normal wiki handler.
When I visit this link, the edit handler draws the different form, which gives us this whole form.
I'll just click save here. It didn't change the content, so we didn't create a new version.
That's how edit works.
Now, of course, we can go to any URL.
It matches the wiki handler first. Let's look at the wiki handler again.
This wiki handler first--remember if the page, if it doesn't exist,
we redirect to /_edit plus the path.
The path is passed in by this regular expression.
We'll go back to our browser.
What's going to happen is we're going to redirect to _edit this string.
There we go. That works.
Now, what if I go something like this.
Actually, let's take a page that already exists. Will this work? Yes.
We get the edit page on this URL.
Remember, our regular expression just basically matches any number of things.
We can actually create a whole complicated URL structure. That's pretty cool.
If I were to say here is the subpage and submit this, it works.
If I were to go back to newpage, that works as well. Pretty cool.