Hi again. Last week I wrote my first blog post which was about cs50's web development course's "Search" project. Now it is time to move on and write my second article about this course. During the last week I have been watching the other lessons and recently submitted the second project which was called "Wiki".
Before moving on, I have to say that again the rest of this article will contain multiple spoilers about this course as last week's article. Please be aware of the academic honesty policy of this course before continuing. I hope you enjoy.
Project 1 -"Wiki"
In this project, we have to create a web application with python and django. This app should be an encyclopedia like wikipedia. Users should be able to see all of the entries, write new entries, and edit an existing entry in the encyclopedia. A distribution code is provided by cs50 to start creating the Wiki. In the distribution code we have a project called "wiki" and an app called "encyclopedia". Rest is up to us.
When I run the distribution code I can see a simple page. On the left side of the page there are search bar, "home", "create new page" and on the main part of the page there are entries. But none of the entries are links and the webpage is static right now. We should be able to click on the entries and go to that page.
First, I simply add an anchor tag for every list item in my "index.html" page which is actually my home page when someone visits this website. With this addition, when someone clicks that link, it will send the user to the "./entry" URL whatever the entry is.
<h1>All Pages</h1>
<ul>
{% for entry in entries %}
<li><a href="/{{ entry }}">{{ entry }}</a></li>
{% endfor %}
</ul>
Before moving to that I also have to tell you what are "entry" and "entries" in that html and I also should show you the URL patterns file. In the given django app we have multiple directories and multiple files. There is a directory for html templates. There is a file called "views_py", there is another file called "urls_py" which you can see the screenshot of (Unfortunately this screenshot is taken after I finished the project and because of that you'll see all the patterns I wrote. In the distribution code there was only one URL pattern which was called index).
Now let me explain. When someone visits the home page which is an empty url, it sends the user to the "views.index" path. Or if someone visits the "new_page" url, the app sends the user to the "views.new" path. Now let's look at the "views_py" file.
The screenshot you can see above is a part of the "views_py" file. There is a function named index, and it renders a html page. While rendering the html page it calls a function (util.list_entries()) and uses its return value as "entries". Did you remember that I mentioned a few minutes ago what are those "entry" and "entries" in the html file? Yes, they were coming from here.
Just a quick summary. Because that is the most important part of the app to understand how django works. User visits a URL -> django looks for that URL pattern in urls_py file -> goes to views_py file and finds the function for that specific URL pattern -> Renders a new html page for that URL in the views_py file.
Now once we have cleared that, let's return to the project. I added an anchor tag for every entry in the index page and it redirects to the "./entry" but that URL is empty right now. Users can hardcode the URL or can click the link for that entry to go to the URL. So I have to create a URL pattern for entries but I don't know what user will write to that URL. How to overcome this? I can not create a pattern for every possible entry. Can I create a pattern that I don't know what it is? Apparently it is possible. Please check the URL patterns screenshot above. There is a path which is like
path("<str:title>", views.get, name="get")
. This is the pattern to use. If you don't know what the user will ask, you can use this as your URL pattern. What is this pattern going to do? It will look at the views_py file and find the "get" function in there. Then I had to write the function of course. What should this "get" function need to do?
-It has to take the "title" argument from the URL.
-It has to use that argument to get the entry from the markdown file which is in the entries directory.
-It has to convert the markdown style entry to html style entry.
-It has to render a html page with that entry.
-If that encyclopedia doesn't have that entry, it should render an error page. Here is the code that I wrote to manage all of the things above. It takes the "title" argument, uses distribution code's util function to get the entry, converts that entry to html using markdown2 module and renders an entry.html page. Now what? Now it's time to create the entry.html page. Here is the html code:
{% block body %}
{% if entry == none %}
<h2>The page can not be found.</h2>
{% else %}
{{ content }}
{% endif %}
{% endblock %}
It should work, right? When I try to use my app and click one of the links it turns out to be something that I didn't expect at that moment. The content was converted to html style but I was seeing the tags, not the implemented version. I was seeing something like this:
After a little research I understand that django needs to know it is safe to render that page. After a small change it turned out as it should be. The code below is the change I made and the result is under that.
{% else %}
{{ content | safe }}
{% endif %}
Search
This project has much more to do. I have to implement the searching, creating new pages and editing. I also need to add a random page link which works. First I need to handle the searching. When someone searches an existing entry, the app should show the content of it. If a user searches only a few letters the app should list all of the entries with those letters. For example if a user types "jang" my page should show "Django" in the main page.
First thing that comes to my mind is to create a new URL route called "search". But then I realised I will still need to be in my index route. The only difference is that there is going to be a submitted form and my request method will be "POST". The index route will render different pages depending on the request method. This is the form:
<form action="{% url 'index' %}" method="post">
{% csrf_token %}
<input class="search" type="text" name="q" placeholder="Search Encyclopedia">
</form>
When this form is submitted, it's request method is going to be "POST" and it will use the "index" path. So I had to change the index function in the views_py file. And while doing this, I learned that I can extract the values from forms using request.POST["..."]
. This was one of the most important thing that I learned while working on this project. Because you know, I started coding only 3.5 months ago. After the changes the function was like this:
If the request method is "POST" and the query is in the entries array it will directly call the entry page using the "get" function. If the query is not in the array it will render the search.html page which you can see below.
{% block body %}
<h1>Search Results</h1>
{% for entry in entries %}
{% if q in entry %}
<li><a href="/{{ entry }}">{{ entry }}</a></li>
{% endif %}
{% endfor %}
{% endblock %}
It will look at all of the entries with a for loop and if the query is inside the entry title it will create an unordered list with those results. This way I finished the index page with both "GET" and "POST" requests. Home page and search page were working as I wanted.
New Page
It is time to create the "new page" link. As before, that link should send me to the "./new_page" URL. That URL must be in my urls_py file and it has to have a path which should look into the views_py file, find the necessary function and finally render a html page. Let's do that.
First I had to add a new URL pattern which was path("new_page", views.new, name="new_page")
inside the urls_py file. After that I had to create the "new" function in the views_py file. The thing about the new function is it has to render two types of html pages. First one is when the user only wants to see the new page page. It only has to have a form, that's all. But when the user actually creates a content with the help of that form and submits it, my new function should create a new entry with its content in the encyclopedia app.
To manage this I used the same way as I did with the index function. If the request method is "GET" it renders the new page form. If the form is submitted with "POST", it takes the values inside the form and uses them to create a new entry. This is the "new.html":
{% block body %}
<form action="{% url 'new_page' %}" method="post">
{% csrf_token %}
<input type="text" name="title" placeholder="Title of the Page">
<input type="submit" name="submit" value="Save the Page">
<hr>
<textarea placeholder="Write your input" name="input"></textarea>
</form>
{% endblock %}
And that is what it look when it is called with "GET":
After the user fills and submits the form, django creates a new entry with this code and calls the "views.get" function to show the entry's page. After finishing my project I realised that it could be better if I redirect the user to the entry's URL directly rather than calling the "views.get" function.
This way my new page link was actively working. I had to implement an edit feature and I added a button for every entry page to edit the entry. Rest of the function was quite similar to new page and I won't write that part too because this article is getting longer and longer.
Random Page
This is the last part of the project. If the user clicks on that link my web application should return a random page. What should my function need to do to manage this?
-It should know how many entries are inside the encyclopedia.
-Choose a random number and find that entry.
-Redirect to that page.
That is how I wrote my code to do it:
That was it. That was my journey while creating the second project. One of my main reasons for writing this blog series is to take notes for myself and to see my progress. And I know it was a very very long note. The other reason is to help people who struggle like me. Even if I can reach only one other person it's something for me. If you are reading this I hope I didn't make you bored, and thank you for coming this far.
Take Home Messages
- I have to use the "safe" filter to render a HTML page with django.
- I can extract the values from forms using request.POST[...]. While doing this I need to write the name of the input tag of the form inside the brackets.
- I should use the power of the "GET" and "POST" request methods.
- If I want to create complex applications, the app will have multiple directories and files which all of them should work together and communicate with each other.
- Before starting the project I procrastinated for two whole days because I thought it was very hard for me. After I started it, it took less than 2 days to finish. Just Start!