Skip to content

Commit 58ea0eb

Browse files
andrepapotivictor-accarini
authored andcommitted
views: Add notes to patch-detail view
The submission template now includes a section to display notes, these can be filtered out depending if the request user is a maintainer for the patch's project and on the note maintainer_only attribute Signed-off-by: andrepapoti <[email protected]>
1 parent cafc714 commit 58ea0eb

File tree

7 files changed

+301
-15
lines changed

7 files changed

+301
-15
lines changed

htdocs/css/style.css

+36-1
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,12 @@ table.patch-meta tr th, table.patch-meta tr td {
340340
border: 0;
341341
}
342342

343+
.submission-message .action-icons {
344+
margin-left: 16px;
345+
display: flex;
346+
gap: 4px;
347+
}
348+
343349
.patch-pull-url {
344350
font-family: "DejaVu Sans Mono", fixed;
345351
}
@@ -449,6 +455,35 @@ div.patch-form {
449455
align-items: center;
450456
}
451457

458+
#edit-note-form {
459+
display: flex;
460+
flex-direction: column;
461+
gap: 10px; /* Space between form elements */
462+
max-width: 100%;
463+
}
464+
465+
#edit-note-form textarea {
466+
flex-grow: 1; /* Allows textarea to grow */
467+
width: 100%;
468+
min-height: 150px;
469+
resize: vertical; /* Allow vertical resizing */
470+
padding: 10px;
471+
box-sizing: border-box;
472+
border: 1px solid #ccc;
473+
border-radius: 5px;
474+
font-size: 1rem;
475+
}
476+
477+
#edit-note-form .form-actions {
478+
display: flex;
479+
justify-content: flex-start; /* Align buttons to the left */
480+
gap: 10px; /* Add space between buttons */
481+
}
482+
#create-note-form div:first-of-type {
483+
display: flex;
484+
flex-direction: column;
485+
}
486+
452487
select[class^=change-property-], .archive-patch-select, .add-bundle {
453488
padding: 4px;
454489
margin-right: 8px;
@@ -476,7 +511,7 @@ select[class^=change-property-], .archive-patch-select, .add-bundle {
476511
padding: 4px;
477512
}
478513

479-
#patch-form-bundle, #add-to-bundle, #remove-bundle {
514+
#patch-form-bundle, #add-to-bundle, #remove-bundle, #create-note {
480515
margin-left: 16px;
481516
}
482517

patchwork/forms.py

+33
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from django.template.backends import django as django_template_backend
1313

1414
from patchwork.models import Bundle
15+
from patchwork.models import Note
1516
from patchwork.models import Patch
1617
from patchwork.models import State
1718
from patchwork.models import UserProfile
@@ -106,6 +107,38 @@ class DeleteBundleForm(forms.Form):
106107
bundle_id = forms.IntegerField(widget=forms.HiddenInput)
107108

108109

110+
class CreateNoteForm(forms.ModelForm):
111+
name = 'createnoteform'
112+
form_name = forms.CharField(initial=name, widget=forms.HiddenInput)
113+
content = forms.CharField(label='Content', widget=forms.Textarea)
114+
maintainer_only = forms.BooleanField(
115+
label='Maintainers Only',
116+
initial=True,
117+
widget=forms.CheckboxInput,
118+
required=False,
119+
)
120+
121+
class Meta:
122+
model = Note
123+
fields = ['content', 'maintainer_only']
124+
125+
126+
class EditNoteForm(forms.ModelForm):
127+
name = 'editnoteform'
128+
form_name = forms.CharField(initial=name, widget=forms.HiddenInput)
129+
content = forms.CharField(label='Content', widget=forms.Textarea)
130+
131+
class Meta:
132+
model = Note
133+
fields = ['content', 'maintainer_only']
134+
135+
136+
class DeleteNoteForm(forms.Form):
137+
name = 'deletenoteform'
138+
form_name = forms.CharField(initial=name, widget=forms.HiddenInput)
139+
note_id = forms.IntegerField(widget=forms.HiddenInput)
140+
141+
109142
class EmailForm(forms.Form):
110143
email = forms.EmailField(max_length=200)
111144

patchwork/templates/patchwork/partials/patch-forms.html

+7
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@
4444
</button>
4545
</div>
4646
{% endif %}
47+
{% if is_maintainer %}
48+
<div id="create-note">
49+
<button class="patch-form-submit btn btn-primary" name="action" value="Add-Note">
50+
Add Note
51+
</button>
52+
</div>
53+
{% endif %}
4754
</div>
4855
{% endif %}
4956
</div>

patchwork/templates/patchwork/submission.html

+64
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
{% load humanize %}
44
{% load syntax %}
55
{% load person %}
6+
{% load user %}
67
{% load patch %}
78
{% load static %}
89
{% load utils %}
910

1011
{% block headers %}
1112
<script type="module" src="{% static "js/submission.js" %}"></script>
13+
<script src="{% static "js/bundle.js" %}"></script>
1214
{% endblock %}
1315

1416
{% block title %}{{submission.name}}{% endblock %}
@@ -136,6 +138,14 @@ <h1>{{ submission.name }}</h1>
136138
{% csrf_token %}
137139
{% include "patchwork/partials/patch-forms.html" %}
138140
</form>
141+
{% if create_note_form %}
142+
<form id="create-note-form" method="post">
143+
<h2>New Note</h2>
144+
{% csrf_token %}
145+
{{ create_note_form.as_div }}
146+
<input type="submit" class="patch-form-submit btn btn-primary" value="Add">
147+
</form>
148+
{% endif %}
139149

140150
{% if submission.pull_url %}
141151
<h2>Pull-request</h2>
@@ -187,6 +197,60 @@ <h2>Message</h2>
187197
</pre>
188198
</div>
189199

200+
{% for note in notes %}
201+
{% if forloop.first %}
202+
<h2>Notes</h2>
203+
{% endif %}
204+
<a name="{{ item.id }}"></a>
205+
<div class="submission-message">
206+
<div class="meta">
207+
User: {{ note.submitter|userfy }},
208+
<span class="message-date">
209+
Last modified: {{ note.updated_at }} UTC
210+
</span>
211+
<div class="action-icons">
212+
{% if is_maintainer %}
213+
<form
214+
method="post"
215+
onsubmit="openEditForm('{{ note.id }}')"
216+
>
217+
{% csrf_token %}
218+
<input type="hidden" name="action" value="edit-note">
219+
<input type="hidden" name="edit_note_id" value="{{ note.id }}">
220+
<button type="submit" style="background: transparent; border: 0px; padding: 0; outline: 0;">
221+
<span class="glyphicon glyphicon-pencil"></span>
222+
</button>
223+
</form>
224+
<form method="post"
225+
onsubmit="return confirm_delete('note', '{{ note.id|escapejs }}');">
226+
{% csrf_token %}
227+
{{ note.delete_form.as_p }}
228+
<button type="submit" style="background: transparent; border: 0px; padding: 0; outline: 0;">
229+
<span class="glyphicon glyphicon-trash"></span>
230+
</button>
231+
</form>
232+
{% endif %}
233+
</div>
234+
</div>
235+
<pre class="content">
236+
{% if edit_note_form and edit_note_id == note.id %}
237+
<form id="edit-note-form" method="post">
238+
{% csrf_token %}
239+
<input type="hidden" name="form_name" value="editnoteform">
240+
<input type="hidden" name="note_id" value="{{ note.id }}">
241+
<textarea name="content">{{ note.content }}</textarea>
242+
<div class="form-actions">
243+
<input type="submit" value="Save" class="btn btn-primary">
244+
<button type="submit" name="cancel" value="Cancel">Cancel</button>
245+
</div>
246+
</form>
247+
{% else %}
248+
{{ note.content }}
249+
{% endif %}
250+
</pre>
251+
</div>
252+
{% endfor %}
253+
190254
{% for item in comments %}
191255
{% if forloop.first %}
192256
<h2>Comments</h2>

patchwork/templatetags/user.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Patchwork - automated patch tracking system
2+
# Copyright (C) 2024 Meta Platforms, Inc. and affiliates.
3+
#
4+
# SPDX-License-Identifier: GPL-2.0-or-later
5+
6+
from django import template
7+
from django.utils.html import escape
8+
from django.utils.safestring import mark_safe
9+
10+
11+
register = template.Library()
12+
13+
14+
@register.filter
15+
def userfy(user):
16+
if user.first_name and user.last_name:
17+
linktext = escape(f'{user.first_name} {user.last_name}')
18+
elif user.email:
19+
linktext = escape(user.email)
20+
else:
21+
linktext = escape(user.username)
22+
23+
return mark_safe(linktext)

patchwork/tests/views/test_patch.py

+50
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from patchwork.models import State
2020
from patchwork.tests.utils import create_check
2121
from patchwork.tests.utils import create_maintainer
22+
from patchwork.tests.utils import create_note
2223
from patchwork.tests.utils import create_patch
2324
from patchwork.tests.utils import create_patch_comment
2425
from patchwork.tests.utils import create_patches
@@ -247,6 +248,55 @@ def test_comment_redirect(self):
247248
response = self.client.get(requested_url)
248249
self.assertRedirects(response, redirect_url)
249250

251+
def test_show_note_for_maintainer(self):
252+
project = create_project()
253+
user = create_maintainer(project)
254+
patch = create_patch(project=project)
255+
note = create_note(patch=patch, submitter=user)
256+
self.client.login(username=user.username, password=user.username)
257+
requested_url = reverse(
258+
'patch-detail',
259+
kwargs={
260+
'project_id': patch.project.linkname,
261+
'msgid': patch.encoded_msgid,
262+
},
263+
)
264+
response = self.client.get(requested_url)
265+
self.assertIn('<h2>Notes</h2>'.encode('utf-8'), response.content)
266+
self.assertIn(note.content.encode('utf-8'), response.content)
267+
268+
def test_hide_private_note(self):
269+
project = create_project()
270+
user = create_maintainer(project)
271+
patch = create_patch(project=project)
272+
note = create_note(patch=patch, submitter=user)
273+
requested_url = reverse(
274+
'patch-detail',
275+
kwargs={
276+
'project_id': patch.project.linkname,
277+
'msgid': patch.encoded_msgid,
278+
},
279+
)
280+
response = self.client.get(requested_url)
281+
self.assertNotIn('<h2>Notes</h2>'.encode('utf-8'), response.content)
282+
self.assertNotIn(note.content.encode('utf-8'), response.content)
283+
284+
def test_show_public_note(self):
285+
project = create_project()
286+
user = create_maintainer(project)
287+
patch = create_patch(project=project)
288+
note = create_note(patch=patch, submitter=user, maintainer_only=False)
289+
requested_url = reverse(
290+
'patch-detail',
291+
kwargs={
292+
'project_id': patch.project.linkname,
293+
'msgid': patch.encoded_msgid,
294+
},
295+
)
296+
response = self.client.get(requested_url)
297+
self.assertIn('<h2>Notes</h2>'.encode('utf-8'), response.content)
298+
self.assertIn(note.content.encode('utf-8'), response.content)
299+
250300
def test_old_detail_url(self):
251301
patch = create_patch()
252302

0 commit comments

Comments
 (0)