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

🎮Implement the functionality to create and edit blog posts #39

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions blog/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Form class for the Blog application."""
webknjaz marked this conversation as resolved.
Show resolved Hide resolved

from django import forms

from .models import Post


class PostForm(forms.ModelForm):
"""Form for the creation of new posts in the Blog application."""
OlenaYefymenko marked this conversation as resolved.
Show resolved Hide resolved

class Meta:
"""Metadata for the post form."""

model = Post
fields = ('title', 'text')
17 changes: 11 additions & 6 deletions blog/templates/blog/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@
</head>
<body>
<header class="page-header">
<div class="container">
<h1><a href="/">Django Girls Blog</a></h1>
</div>
<div class="container">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you change the indentation here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edited

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What did you edit exactly and how?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry I didn't understand what I should correct

Copy link
Member

@webknjaz webknjaz Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restore the original indentation, I suppose.

{% if user.is_authenticated %}
<a href="{% url 'post_new' %}" class="top-menu">
{% include './icons/file-earmark-plus.svg' %}
</a>
{% endif %}
<h1><a href="/">Django Girls Blog</a></h1>
</div>
</header>
<main class="container">
<main class="content container">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this relate to editing the posts?

<div class="row">
<div class="col">
{% block content %}
{% endblock %}
{% block content %}
{% endblock %}
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
</div>
</div>
</main>
Expand Down
4 changes: 4 additions & 0 deletions blog/templates/blog/icons/file-earmark-plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions blog/templates/blog/icons/pencil-fill.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions blog/templates/blog/post_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

{% block content %}
<article class="post">
<aside class="actions">
{% if user.is_authenticated %}
<a class="btn btn-secondary" href="{% url 'post_edit' pk=post.pk %}">
{% include './icons/pencil-fill.svg' %}
</a>
{% endif %}
</aside>
{% if post.published_date %}
<time class="date">
{{ post.published_date }}
Expand Down
9 changes: 9 additions & 0 deletions blog/templates/blog/post_edit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% extends 'blog/base.html' %}

{% block content %}
<h2>New post</h2>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-secondary">Save</button>
</form>
{% endblock %}
26 changes: 26 additions & 0 deletions blog/templates/registration/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% extends "blog/base.html" %}

{% block content %}

{% if form.errors %}
<p>Your username and password didn't match. Please try again.
OlenaYefymenko marked this conversation as resolved.
Show resolved Hide resolved
{% endif %}

<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
</table>

<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>

{% endblock %}
5 changes: 5 additions & 0 deletions blog/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
"""The URL configuration for the Blog application."""

from django.contrib.auth import views as auth_views
from django.urls import path

from . import views

urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<int:pk>/', views.post_detail, name='post_detail'),
path('post/new/', views.post_new, name='post_new'),
path('post/<int:pk>/edit/', views.post_edit, name='post_edit'),
path('accounts/login/', auth_views.LoginView.as_view(), name='login'),

OlenaYefymenko marked this conversation as resolved.
Show resolved Hide resolved
]
37 changes: 36 additions & 1 deletion blog/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""The views for the Blog application."""

from django.shortcuts import get_object_or_404, render
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone

from .forms import PostForm
from .models import Post


Expand All @@ -20,3 +22,36 @@ def post_detail(request, pk):
"""Display a blog post based on the post's primary key."""
post = get_object_or_404(Post, pk=pk)
return render(request, 'blog/post_detail.html', {'post': post})


@login_required
def post_new(request):
"""Create a new post."""
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.published_date = timezone.now()
post.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'blog/post_edit.html', {'form': form})


@login_required
def post_edit(request, pk):
"""Edit an existing post."""
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
form = PostForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.published_date = timezone.now()
post.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, 'blog/post_edit.html', {'form': form})
170 changes: 169 additions & 1 deletion tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ class ViewsTest(TestCase):
@classmethod
def setUpTestData(cls):
"""Create test client, user, posts with different published dates."""
cls.user = get_user_model().objects.create()
cls.user = get_user_model().objects.create_user(
username='test user',
password='test password',
)
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
cls.client = Client()
cls.tm_zone = timezone.get_current_timezone()

Expand Down Expand Up @@ -86,3 +89,168 @@ def test_post_detail_failed(self):
reverse('post_detail', kwargs={'pk': 31}),
)
self.assertEqual(404, http_response.status_code)

def test_create_new_post_authorized_user(self):
"""Ensure the correct creation of the new post for authorized user."""
login_successful = self.client.login(
username='test user', password='test password',
)
self.assertTrue(login_successful)
# GET request
get_response = self.client.get(reverse('post_new'))
self.assertEqual(200, get_response.status_code)
self.assertTemplateUsed(get_response, 'blog/post_edit.html')

# POST request with valid form data
valid_form_post_data = {
'title': 'test title',
'text': 'test text',
}
post_response_valid = self.client.post(
reverse('post_new'), valid_form_post_data, follow=True,
)
self.assertEqual(200, post_response_valid.status_code)
new_post = Post.objects.filter(
title='test title', text='test text',
).first()
self.assertIsNotNone(new_post)

self.assertRedirects(
post_response_valid, reverse(
'post_detail', kwargs={'pk': new_post.pk},
),
)
self.assertTemplateUsed(post_response_valid, 'blog/post_detail.html')

# POST request with invalid form data
invalid_form_data = {
'title': '',
'text': 'test text',
}
post_response_invalid = self.client.post(
reverse(
'post_new',
), data=invalid_form_data, follow=True,
)
self.assertEqual(200, post_response_invalid.status_code)
self.assertTemplateUsed(post_response_invalid, 'blog/post_edit.html')
self.assertContains(
post_response_invalid,
'This field is required', count=1,
)

def test_create_new_post_unauthorized_user(self):
"""Ensure the unauthorized user is redirected to the login page."""
response = self.client.get(reverse('post_new'))
self.assertRedirects(
response, f'/accounts/login/?next={reverse("post_new")}',
)

def test_post_edit_authorized_user(self):
"""Ensure."""
login_successful = self.client.login(
username='test user', password='test password',
)
self.assertTrue(login_successful)
post = Post.objects.create(
author=self.user, title='test title', text='test text',
)

# GET response
get_response = self.client.get(
reverse('post_edit', kwargs={'pk': post.pk}),
)
self.assertEqual(200, get_response.status_code)
self.assertTemplateUsed(get_response, 'blog/post_edit.html')

# POST request with valid form data
valid_form_data = {
'title': 'edited test title',
'text': 'edited test text',
}
post_response_valid = self.client.post(
reverse('post_edit', kwargs={'pk': post.pk}),
valid_form_data, follow=True,
)
self.assertEqual(200, post_response_valid.status_code)
edited_post = Post.objects.get(pk=post.pk)
self.assertEqual(edited_post.title, 'edited test title')
self.assertEqual(edited_post.text, 'edited test text')

self.assertRedirects(
post_response_valid, reverse(
'post_detail', kwargs={'pk': post.pk},
),
)
self.assertTemplateUsed(
post_response_valid,
'blog/post_detail.html',
)

# POST request with invalid form data
invalid_form_data = {
'title': '',
'text': 'edited test text',
}
post_response_invalid = self.client.post(
reverse('post_edit', kwargs={'pk': post.pk}),
data=invalid_form_data,
)
self.assertEqual(200, post_response_invalid.status_code)
self.assertTemplateUsed(post_response_invalid, 'blog/post_edit.html')
self.assertContains(
post_response_invalid,
'This field is required', count=1,
)
post.refresh_from_db()
self.assertEqual(post.title, 'edited test title')
self.assertEqual(post.text, 'edited test text')

def test_post_edit_unauthorized_user(self):
"""Ensure."""
post = Post.objects.create(
author=self.user, title='test post', text='test text',
)
response = self.client.get(
reverse('post_edit', kwargs={'pk': post.pk}),
)
self.assertRedirects(
response,
f'/accounts/login/?next={reverse("post_edit", kwargs={"pk": post.pk})}',
)

def test_post_edit_successful(self):
"""Ensure."""
login_successful = self.client.login(
username='test user', password='test password',
)
self.assertTrue(login_successful)

post = Post.objects.create(
author=self.user, title='test post', text='test text',
)
valid_form_data = {
'title': 'edited test title',
'text': 'edited test title',
}
post_response = self.client.post(
reverse(
'post_edit', kwargs={'pk': post.pk},
), valid_form_data, follow=True,
)
self.assertEqual(post_response.status_code, 200)
post.refresh_from_db()
self.assertEqual(post.title, 'edited test title')
self.assertEqual(post.text, 'edited test title')

def test_post_edit_failed(self):
"""Ensure."""
login_successful = self.client.login(
username='test user', password='test password',
)
self.assertTrue(login_successful)

http_response = self.client.get(
reverse('post_edit', kwargs={'pk': 33}),
)
self.assertEqual(404, http_response.status_code)
Loading