Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'Security' content added, updated icon links/html, and general formatting changes #175

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
58 changes: 47 additions & 11 deletions en/authentication_authorization/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ First let's make things secure. We will protect our `post_new`, `post_edit`, `po

So edit your `blog/views.py` and add these lines at the top along with the rest of the imports:

{% filename %}blog/views.py{% endfilename %}
```python
from django.contrib.auth.decorators import login_required
```

Then add a line before each of the `post_new`, `post_edit`, `post_draft_list`, `post_remove` and `post_publish` views (decorating them) like the following:

{% filename %}blog/views.py{% endfilename %}
```python
@login_required
def post_new(request):
Expand All @@ -37,6 +39,7 @@ We could now try to do lots of magical stuff to implement users and passwords an

In your `mysite/urls.py` add a url `path('accounts/login/', views.LoginView.as_view(), name='login')`. So the file should now look similar to this:

{% filename %}mysite/urls.py{% endfilename %}
```python
from django.urls import path, include
from django.contrib import admin
Expand All @@ -52,6 +55,7 @@ urlpatterns = [

Then we need a template for the login page, so create a directory `blog/templates/registration` and a file inside named `login.html`:

{% filename %}blog/templates/registration/login.html{% endfilename %}
```django
{% extends "blog/base.html" %}

Expand Down Expand Up @@ -83,6 +87,7 @@ You will see that this also makes use of our _base_ template for the overall loo

The nice thing here is that this _just works<sup>TM</sup>_. We don't have to deal with handling of the form submission nor with passwords and securing them. Only more thing is left to do. We should add a setting to `mysite/settings.py`:

{% filename %}mysite/settings.py{% endfilename %}
```python
LOGIN_REDIRECT_URL = '/'
```
Expand All @@ -93,22 +98,27 @@ so that when the login page is accessed directly, it will redirect a successful

We already set things up so that only authorized users (i.e. us) see the buttons for adding and editing posts. Now we want to make sure a login button appears for everybody else.

We will add a login button that looks like this:
We will add a login button using an unlock icon.

Download the unlock image from [https://icons.getbootstrap.com/assets/icons/unlock.svg](https://icons.getbootstrap.com/assets/icons/unlock.svg) and save it in the folder `blog/templates/registration/icons/`

We will add the login button like this

```django
<a href="{% url 'login' %}" class="top-menu"><span class="glyphicon glyphicon-lock"></span></a>
<a href="{% url 'login' %}" class="top-menu">{% include 'registration/icons/unlock.svg' %}</a>
```

For this we need to edit the templates, so let's open up `blog/templates/blog/base.html` and change it so the part between the `<body>` tags looks like this:
We want to ensure the button is only visible to non-authenticated users, so let's open up `base.html` and change it so the part between the `<body>` tags looks like this:

{% filename %}blog/templates/blog/base.html{% endfilename %}
```django
<body>
<div class="page-header">
{% if user.is_authenticated %}
<a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
<a href="{% url 'post_draft_list' %}" class="top-menu"><span class="glyphicon glyphicon-edit"></span></a>
<a href="{% url 'post_new' %}" class="top-menu">{% include './icons/file-earmark-plus.svg' %}</a>
<a href="{% url 'post_draft_list' %}" class="top-menu">{% include './icons/pencil-fill.svg' %}</a>
{% else %}
<a href="{% url 'login' %}" class="top-menu"><span class="glyphicon glyphicon-lock"></span></a>
<a href="{% url 'login' %}" class="top-menu">{% include 'registration/icons/unlock.svg' %}</a>
{% endif %}
<h1><a href="/">Django Girls Blog</a></h1>
</div>
Expand All @@ -123,20 +133,21 @@ For this we need to edit the templates, so let's open up `blog/templates/blog/ba
</body>
```

You might recognize the pattern here. There is an if-condition in the template that checks for authenticated users to show the add and edit buttons. Otherwise it shows a login button.
You might recognize the pattern here. There is an if-condition in the template that checks for authenticated users to show the add and edit buttons. {% raw %}`{% else %}`{% endraw %} it shows a login button.

## More on authenticated users

Let's add some sugar to our templates while we're at it. First we will add some details to show when we are logged in. Edit `blog/templates/blog/base.html` like this:
Let's add some sugar to our templates while we're at it. First we will add some details to show when we are logged in. Edit `base.html` like this:

{% filename %}blog/templates/blog/base.html{% endfilename %}
```django
<div class="page-header">
{% if user.is_authenticated %}
<a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
<a href="{% url 'post_draft_list' %}" class="top-menu"><span class="glyphicon glyphicon-edit"></span></a>
<a href="{% url 'post_new' %}" class="top-menu">{% include './icons/file-earmark-plus.svg' %}</a>
<a href="{% url 'post_draft_list' %}" class="top-menu">{% include './icons/pencil-fill.svg' %}</a>
<p class="top-menu">Hello {{ user.username }} <small>(<a href="{% url 'logout' %}">Log out</a>)</small></p>
{% else %}
<a href="{% url 'login' %}" class="top-menu"><span class="glyphicon glyphicon-lock"></span></a>
<a href="{% url 'login' %}" class="top-menu">{% include 'registration/icons/unlock.svg' %}</a>
{% endif %}
<h1><a href="/">Django Girls Blog</a></h1>
</div>
Expand All @@ -148,6 +159,7 @@ We decided to rely on Django to handle login, so let's see if Django can also ha

Done reading? By now you may be thinking about adding a URL in `mysite/urls.py` pointing to Django's logout view (i.e. `django.contrib.auth.views.logout`), like this:

{% filename %}mysite/urls.py{% endfilename %}
```python
from django.urls import path, include
from django.contrib import admin
Expand All @@ -162,6 +174,30 @@ urlpatterns = [
]
```

## Is there anything missing?

We made sure non-logged in users can't access the `post_draft_list` view by using the `@login_required` decorator. We also decorated the `post_edit`, `post_remove` and `post_publish` views so they can't make changes. But could a non-logged in user still see a draft post?

While logged out, try navigating to a draft post by editing the url address bar directly. Whoops! We can still see the draft post!

This is because we use the `post_detail()` view method for both published and draft posts. We need to protect this view as well. We definitely want non-logged in users to see published posts, so using the decorator won't work.

Instead, let's go to the `blog/views.py` file and update the `post_detail()` view to check for a `published_date` or whether a `user` `is_authenticated`. If neither condition is true, we will redirect the user to login.

{% filename %}blog/views.py{% endfilename %}
```
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
if post.published_date or request.user.is_authenticated:
return render(request, 'blog/post_detail.html', {'post': post})
else:
return redirect("/accounts/login/")
```

Check again if you can navigate directly to a draft post using the address bar.



That's it! If you followed all of the above up to this point (and did the homework), you now have a blog where you

- need a username and password to log in,
Expand Down
45 changes: 29 additions & 16 deletions en/homework/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Our blog has come a long way but there's still room for improvement. Next, we wi

Currently when we're creating new posts using our *New post* form the post is published directly. To instead save the post as a draft, **remove** this line in `blog/views.py` in the `post_new` and `post_edit` methods:

{% filename %}blog/views.py{% endfilename %}
```python
post.published_date = timezone.now()
```
Expand All @@ -18,20 +19,23 @@ Remember the chapter about querysets? We created a view `post_list` that display

Time to do something similar, but for draft posts.

Let's add a link in `blog/templates/blog/base.html` in the header. We don't want to show our list of drafts to everybody, so we'll put it inside the {% raw %}`{% if user.is_authenticated %}`{% endraw %} check, right after the button for adding new posts.
Let's add a link to the header.of our `base.html` template. We don't want to show our list of drafts to everybody, so we'll put it inside the {% raw %}`{% if user.is_authenticated %}`{% endraw %} check, right after the button for adding new posts.

{% filename %}blog/templates/blog/base.html{% endfilename %}
```django
<a href="{% url 'post_draft_list' %}" class="top-menu"><span class="glyphicon glyphicon-edit"></span></a>
<a href="{% url 'post_draft_list' %}" class="top-menu">{% include './icons/pencil-fill.svg' %}</a>
```

Next: urls! In `blog/urls.py` we add:
Next we define the url path!

{% filename %}blog/urls.py{% endfilename %}
```python
path('drafts/', views.post_draft_list, name='post_draft_list'),
```

Time to create a view in `blog/views.py`:
Time to create a view:

{% filename %}blog/views.py{% endfilename %}
```python
def post_draft_list(request):
posts = Post.objects.filter(published_date__isnull=True).order_by('created_date')
Expand All @@ -40,8 +44,9 @@ def post_draft_list(request):

The line ` posts = Post.objects.filter(published_date__isnull=True).order_by('created_date')` makes sure that we take only unpublished posts (`published_date__isnull=True`) and order them by `created_date` (`order_by('created_date')`).

Ok, the last bit is of course a template! Create a file `blog/templates/blog/post_draft_list.html` and add the following:
Ok, the last bit is of course a template! Create a new template file `post_draft_list.html` and add the following:

{% filename %}blog/templates/blog/post_draft_list.html{% endfilename %}
```django
{% extends 'blog/base.html' %}

Expand All @@ -66,8 +71,9 @@ Yay! Your first task is done!

It would be nice to have a button on the blog post detail page that will immediately publish the post, right?

Let's open `blog/templates/blog/post_detail.html` and change these lines:
Let's open `post_detail.html` and change these lines:

{% filename %}blog/templates/blog/post_detail.html{% endfilename %}
```django
{% if post.published_date %}
<div class="date">
Expand All @@ -78,26 +84,29 @@ Let's open `blog/templates/blog/post_detail.html` and change these lines:

into these:

{% filename %}blog/templates/blog/post_detail.html{% endfilename %}
```django
{% if post.published_date %}
<div class="date">
{{ post.published_date }}
</div>
{% else %}
<a class="btn btn-default" href="{% url 'post_publish' pk=post.pk %}">Publish</a>
<a class="btn btn-secondary" href="{% url 'post_publish' pk=post.pk %}">Publish</a>
{% endif %}
```

As you noticed, we added {% raw %}`{% else %}`{% endraw %} line here. That means, that if the condition from {% raw %}`{% if post.published_date %}`{% endraw %} is not fulfilled (so if there is no `published_date`), then we want to do the line {% raw %}`<a class="btn btn-default" href="{% url 'post_publish' pk=post.pk %}">Publish</a>`{% endraw %}. Note that we are passing a `pk` variable in the {% raw %}`{% url %}`{% endraw %}.
As you noticed, we added {% raw %}`{% else %}`{% endraw %} line here. That means, that if the condition from {% raw %}`{% if post.published_date %}`{% endraw %} is not fulfilled (so if there is no `published_date`), then we want to do the line {% raw %}`<a class="btn btn-secondary" href="{% url 'post_publish' pk=post.pk %}">Publish</a>`{% endraw %}. Note that we are passing a `pk` variable in the {% raw %}`{% url %}`{% endraw %}.

Time to create a URL (in `blog/urls.py`):
Time to create a URL:

{% filename %}blog/urls.py{% endfilename %}
```python
path('post/<pk>/publish/', views.post_publish, name='post_publish'),
```

and finally, a *view* (as always, in `blog/views.py`):
and finally, a *view*:

{% filename %}blog/views.py{% endfilename %}
```python
def post_publish(request, pk):
post = get_object_or_404(Post, pk=pk)
Expand All @@ -107,6 +116,7 @@ def post_publish(request, pk):

Remember, when we created a `Post` model we wrote a method `publish`. It looked like this:

{% filename %}blog/models.py{% endfilename %}
```python
def publish(self):
self.published_date = timezone.now()
Expand All @@ -123,22 +133,25 @@ Congratulations! You are almost there. The last step is adding a delete button!

## Delete post

Let's open `blog/templates/blog/post_detail.html` once again and add this line:
First let's download a trash icon and save with others in `blog/templates/blog/icons/`: [https://icons.getbootstrap.com/assets/icons/trash.svg](https://icons.getbootstrap.com/assets/icons/trash.svg)

Let's open `post_detail.html` once again and add this line just under the line with the edit button:

{% filename %}blog/templates/blog/post_detail.html{% endfilename %}
```django
<a class="btn btn-default" href="{% url 'post_remove' pk=post.pk %}"><span class="glyphicon glyphicon-remove"></span></a>
<a class="btn btn-secondary" href="{% url 'post_remove' pk=post.pk %}">{% include './icons/trash3.svg' %}</a>
```

just under a line with the edit button.

Now we need a URL (`blog/urls.py`):
Now we need a URL:

{% filename %}blog/urls.py{% endfilename %}
```python
path('post/<pk>/remove/', views.post_remove, name='post_remove'),
```

Now, time for a view! Open `blog/views.py` and add this code:
Now, time for a view!

{% filename %}blog/views.py{% endfilename %}
```python
def post_remove(request, pk):
post = get_object_or_404(Post, pk=pk)
Expand Down
Loading