Merge pull request 'feature/comment-page' (#16) from feature/comment-page into development
All checks were successful
SonarQube Scan / SonarQube Trigger (push) Successful in 45s
All checks were successful
SonarQube Scan / SonarQube Trigger (push) Successful in 45s
Reviewed-on: #16
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- development
|
||||
# branches:
|
||||
# - main
|
||||
# - development
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
|
||||
66
dokumente/templates/standards/user_comments.html
Normal file
66
dokumente/templates/standards/user_comments.html
Normal file
@@ -0,0 +1,66 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Meine Kommentare</h1>
|
||||
|
||||
{% if total_comments == 0 %}
|
||||
<div class="alert alert-info">
|
||||
<p>Sie haben noch keine Kommentare zu Vorgaben hinterlassen.</p>
|
||||
<p><a href="{% url 'standard_list' %}">Zu den Standards</a></p>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted">Insgesamt {{ total_comments }} Kommentar{{ total_comments|pluralize:"e" }}</p>
|
||||
|
||||
{% for dokument, comments in comments_by_document.items %}
|
||||
<div class="panel panel-default" style="margin-top: 2rem;">
|
||||
<div class="panel-heading">
|
||||
<h2 style="margin: 0;">
|
||||
<a href="{% url 'standard_detail' nummer=dokument.nummer %}">
|
||||
{{ dokument.nummer }} – {{ dokument.name }}
|
||||
</a>
|
||||
</h2>
|
||||
<p style="margin: 0; color: #666; font-size: 0.9rem;">
|
||||
{{ comments|length }} Kommentar{{ comments|length|pluralize:"e" }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="list-group">
|
||||
{% for comment in comments %}
|
||||
<div class="list-group-item" style="border-left: 3px solid #007bff; padding: 1rem;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
<div style="flex: 1;">
|
||||
<h4 style="margin: 0 0 0.5rem 0;">
|
||||
<a href="{% url 'standard_detail' nummer=comment.vorgabe.dokument.nummer %}#{{ comment.vorgabe.Vorgabennummer }}">
|
||||
{{ comment.vorgabe.Vorgabennummer }}
|
||||
</a>
|
||||
</h4>
|
||||
<p style="margin: 0 0 0.75rem 0; color: #666; font-size: 0.9rem;">
|
||||
<strong>Erstellt:</strong> {{ comment.created_at|date:"d.m.Y H:i" }}
|
||||
{% if comment.updated_at != comment.created_at %}
|
||||
<br>
|
||||
<strong>Bearbeitet:</strong> {{ comment.updated_at|date:"d.m.Y H:i" }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<form method="POST" action="{% url 'delete_vorgabe_comment' comment.id %}"
|
||||
style="display: inline; margin-left: 1rem;"
|
||||
onsubmit="return confirm('Sind Sie sicher, dass Sie diesen Kommentar löschen möchten?');">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm btn-danger">Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
<div style="background: #f8f9fa; padding: 0.75rem; border-radius: 4px; margin-top: 0.5rem; white-space: pre-wrap; word-wrap: break-word;">
|
||||
{{ comment.text }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div style="margin-top: 2rem; padding-top: 2rem; border-top: 1px solid #ddd;">
|
||||
<a href="{% url 'standard_list' %}" class="btn btn-default">Zu den Standards</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -2182,3 +2182,164 @@ class DeleteVorgabeCommentViewTest(TestCase):
|
||||
self.assertIn('Content-Security-Policy', response)
|
||||
self.assertIn('X-Content-Type-Options', response)
|
||||
self.assertEqual(response['X-Content-Type-Options'], 'nosniff')
|
||||
|
||||
|
||||
class UserCommentsViewTest(TestCase):
|
||||
"""Test the user comments view that displays all comments grouped by document"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test data"""
|
||||
# Create users
|
||||
self.user1 = User.objects.create_user(username='user1', password='pass123')
|
||||
self.user2 = User.objects.create_user(username='user2', password='pass123')
|
||||
|
||||
# Create documents
|
||||
self.doc_type = Dokumententyp.objects.create(name='Test Type', verantwortliche_ve='test')
|
||||
self.doc1 = Dokument.objects.create(nummer='DOC-001', name='Document 1', dokumententyp=self.doc_type, aktiv=True)
|
||||
self.doc2 = Dokument.objects.create(nummer='DOC-002', name='Document 2', dokumententyp=self.doc_type, aktiv=True)
|
||||
|
||||
# Create themes
|
||||
self.theme1 = Thema.objects.create(name='Theme 1')
|
||||
self.theme2 = Thema.objects.create(name='Theme 2')
|
||||
|
||||
# Create vorgaben
|
||||
from datetime import date
|
||||
self.vorgabe1 = Vorgabe.objects.create(
|
||||
nummer=1,
|
||||
order=1,
|
||||
dokument=self.doc1,
|
||||
thema=self.theme1,
|
||||
titel='Vorgabe 1',
|
||||
gueltigkeit_von=date.today()
|
||||
)
|
||||
self.vorgabe2 = Vorgabe.objects.create(
|
||||
nummer=2,
|
||||
order=2,
|
||||
dokument=self.doc1,
|
||||
thema=self.theme1,
|
||||
titel='Vorgabe 2',
|
||||
gueltigkeit_von=date.today()
|
||||
)
|
||||
self.vorgabe3 = Vorgabe.objects.create(
|
||||
nummer=1,
|
||||
order=1,
|
||||
dokument=self.doc2,
|
||||
thema=self.theme2,
|
||||
titel='Vorgabe 3',
|
||||
gueltigkeit_von=date.today()
|
||||
)
|
||||
|
||||
# Create comments for user1
|
||||
self.comment1 = VorgabeComment.objects.create(
|
||||
vorgabe=self.vorgabe1,
|
||||
user=self.user1,
|
||||
text='User1 comment on vorgabe1'
|
||||
)
|
||||
self.comment2 = VorgabeComment.objects.create(
|
||||
vorgabe=self.vorgabe2,
|
||||
user=self.user1,
|
||||
text='User1 comment on vorgabe2'
|
||||
)
|
||||
self.comment3 = VorgabeComment.objects.create(
|
||||
vorgabe=self.vorgabe3,
|
||||
user=self.user1,
|
||||
text='User1 comment on vorgabe3'
|
||||
)
|
||||
|
||||
# Create comment for user2
|
||||
self.comment4 = VorgabeComment.objects.create(
|
||||
vorgabe=self.vorgabe1,
|
||||
user=self.user2,
|
||||
text='User2 comment on vorgabe1'
|
||||
)
|
||||
|
||||
def test_user_comments_requires_login(self):
|
||||
"""Test that user comments view requires authentication"""
|
||||
response = self.client.get(reverse('user_comments'))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertIn('/login/', response.url)
|
||||
|
||||
def test_user_comments_shows_only_own_comments(self):
|
||||
"""Test that user only sees their own comments"""
|
||||
self.client.login(username='user1', password='pass123')
|
||||
response = self.client.get(reverse('user_comments'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'User1 comment on vorgabe1')
|
||||
self.assertContains(response, 'User1 comment on vorgabe2')
|
||||
self.assertContains(response, 'User1 comment on vorgabe3')
|
||||
self.assertNotContains(response, 'User2 comment on vorgabe1')
|
||||
|
||||
def test_user_comments_grouped_by_document(self):
|
||||
"""Test that comments are properly grouped by document"""
|
||||
self.client.login(username='user1', password='pass123')
|
||||
response = self.client.get(reverse('user_comments'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# Check that document titles appear
|
||||
self.assertContains(response, 'DOC-001 – Document 1')
|
||||
self.assertContains(response, 'DOC-002 – Document 2')
|
||||
|
||||
# Check context
|
||||
self.assertIn('comments_by_document', response.context)
|
||||
self.assertEqual(len(response.context['comments_by_document']), 2)
|
||||
|
||||
def test_user_comments_count_display(self):
|
||||
"""Test that total comment count is displayed"""
|
||||
self.client.login(username='user1', password='pass123')
|
||||
response = self.client.get(reverse('user_comments'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context['total_comments'], 3)
|
||||
self.assertContains(response, '3 Kommentare')
|
||||
|
||||
def test_user_comments_empty_view(self):
|
||||
"""Test the view when user has no comments"""
|
||||
# Create a new user with no comments
|
||||
user3 = User.objects.create_user(username='user3', password='pass123')
|
||||
self.client.login(username='user3', password='pass123')
|
||||
response = self.client.get(reverse('user_comments'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context['total_comments'], 0)
|
||||
self.assertContains(response, 'Sie haben noch keine Kommentare')
|
||||
|
||||
def test_user_comments_comment_text_preserved(self):
|
||||
"""Test that comment text is correctly displayed"""
|
||||
self.client.login(username='user1', password='pass123')
|
||||
response = self.client.get(reverse('user_comments'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# Check that comment text appears in response
|
||||
self.assertContains(response, 'User1 comment on vorgabe1')
|
||||
|
||||
def test_user_comments_vorgabe_number_link(self):
|
||||
"""Test that vorgabe numbers are linked correctly"""
|
||||
self.client.login(username='user1', password='pass123')
|
||||
response = self.client.get(reverse('user_comments'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# Check that vorgabe numbers appear (format is DOC-001.T.1)
|
||||
self.assertContains(response, 'DOC-001.T.1')
|
||||
self.assertContains(response, 'DOC-001.T.2')
|
||||
self.assertContains(response, 'DOC-002.T.1')
|
||||
|
||||
def test_user_comments_ordered_by_creation_date(self):
|
||||
"""Test that comments are ordered by creation date (newest first)"""
|
||||
self.client.login(username='user1', password='pass123')
|
||||
response = self.client.get(reverse('user_comments'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# The queryset orders by vorgabe document, then by -created_at
|
||||
# Check that all three comments are in the response
|
||||
self.assertContains(response, 'User1 comment on vorgabe1')
|
||||
self.assertContains(response, 'User1 comment on vorgabe2')
|
||||
self.assertContains(response, 'User1 comment on vorgabe3')
|
||||
|
||||
def test_user_comments_template_used(self):
|
||||
"""Test that correct template is used"""
|
||||
self.client.login(username='user1', password='pass123')
|
||||
response = self.client.get(reverse('user_comments'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'standards/user_comments.html')
|
||||
|
||||
@@ -4,6 +4,7 @@ from . import views
|
||||
urlpatterns = [
|
||||
path('', views.standard_list, name='standard_list'),
|
||||
path('unvollstaendig/', views.incomplete_vorgaben, name='incomplete_vorgaben'),
|
||||
path('meine-kommentare/', views.user_comments, name='user_comments'),
|
||||
path('<str:nummer>/', views.standard_detail, name='standard_detail'),
|
||||
path('<str:nummer>/history/<str:check_date>/', views.standard_detail),
|
||||
path('<str:nummer>/history/', views.standard_detail, {"check_date":"today"}, name='standard_history'),
|
||||
|
||||
@@ -366,3 +366,29 @@ def delete_vorgabe_comment(request, comment_id):
|
||||
response['Content-Security-Policy'] = "default-src 'self'"
|
||||
response['X-Content-Type-Options'] = 'nosniff'
|
||||
return response
|
||||
|
||||
|
||||
@login_required
|
||||
def user_comments(request):
|
||||
"""
|
||||
Display all comments made by the logged-in user, grouped by document.
|
||||
"""
|
||||
# Get all comments by the current user
|
||||
user_comments = VorgabeComment.objects.filter(
|
||||
user=request.user
|
||||
).select_related('vorgabe', 'vorgabe__dokument').order_by(
|
||||
'vorgabe__dokument__nummer', '-created_at'
|
||||
)
|
||||
|
||||
# Group comments by document
|
||||
comments_by_document = {}
|
||||
for comment in user_comments:
|
||||
dokument = comment.vorgabe.dokument
|
||||
if dokument not in comments_by_document:
|
||||
comments_by_document[dokument] = []
|
||||
comments_by_document[dokument].append(comment)
|
||||
|
||||
return render(request, 'standards/user_comments.html', {
|
||||
'comments_by_document': comments_by_document,
|
||||
'total_comments': user_comments.count(),
|
||||
})
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<span class="caret" style="margin-left: 8px;"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li><a href="{% url 'user_comments' %}">Meine Kommentare</a></li>
|
||||
<li><a href="{% url 'password_change' %}">Passwort ändern</a></li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
|
||||
Reference in New Issue
Block a user