Skip to content

Commit 5ff5167

Browse files
Merge pull request #835 from GhostManager/hotfix/report-template-fixes
Further Improve PowerPoint and Fix Admin Links
2 parents a2a926e + a073631 commit 5ff5167

File tree

21 files changed

+511
-138
lines changed

21 files changed

+511
-138
lines changed

CHANGELOG.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [6.2.6] - 5 March 2026
9+
10+
### Added
11+
12+
* Added a "Download" link to the admin console for models with a file field — report templates, user profiles, and evidence
13+
14+
### Changed
15+
16+
* Updated PowerPoint slide generation to attempt to intelligently adjust for different slide layouts (Fixes #836)
17+
* If shape indices are non-sequential (can happen when shapes are deleted), Ghostwriter will fallback to searching
18+
* If there are multiple placeholders, it will try to find the first with the content type (7 or 17)
19+
* Ghostwriter now uses the final layout for the final slide instead of expecting it at position 12
20+
21+
### Fixed
22+
23+
* Fixed file field links in the admin console returning a 404 Not Found after recent changes to media links (Fixes #837)
24+
825
## [6.2.5] - 3 March 2026
926

1027
### Changed
@@ -14,7 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1431

1532
### Fixed
1633

17-
* Fixed PowerPoint generation failing with some templates when slide layout content did not match expectation
34+
* Fixed PowerPoint generation failing with some templates when slide layout content did not match expectation (Fixes #836)
1835
* Fixed domain and server history not properly showing the "Checked Out By" column
1936
* Fixed updating a domain or server checkout setting the user value to null
2037
* Fixed some contrast issues with the objectives table and dark mode

DOCS/features/reporting/report-types/powerpoint-deck-customization.mdx

Lines changed: 87 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,84 +5,127 @@ description: "Customizing PowerPoint slide deck generation"
55

66
## Getting Started with PowerPoint
77

8-
Ghostwriter uses template documents and the Jinja2 template language ([https://jinja.palletsprojects.com/en/2.11.x/](https://jinja.palletsprojects.com/en/2.11.x/)) to give you as much control over document generation as possible.
8+
Ghostwriter uses template documents and the Jinja2 template language ([https://jinja.palletsprojects.com/en/stable/](https://jinja.palletsprojects.com/en/stable/))
9+
to give you as much control over document generation as possible.
910

10-
Learn more about managing report templates here:
11+
Learn more about managing report templates here: [Report Templates](/features/reporting/report-templates)
1112

12-
[Report Templates](/features/reporting/report-templates)
13+
You will need to upload at least one PowerPoint slide deck to use as a template. Once you have a template you can pick from, open your report and
14+
select the template from the dropdown menu under the **Generate Reports** section. You should see a notification when the template selection is saved.
15+
You can then click the PowerPoint icon to generate a report.
1316

14-
You will need to upload at least one PowerPoint slide deck to use as a template. Once you have a template you can pick from, open your report and select the template from the dropdown menu under the **Generate Reports** section. You should see a notification when the template selection is saved. You can then click the PowerPoint icon to generate a report.
15-
16-
Depending on the size of your report and template, rendering can take a few seconds. Once the report is done, your browser will download a new PowerPoint document.
17+
Depending on the size of your report and template, rendering can take a few seconds. Once the report is done, your browser will download a new PowerPoint
18+
document.
1719

1820
The default filename will be: `YYYYMMDD_HHMMSS_CLIENT-NAME_ASESSMENT-TYPE.pptx`
1921

2022
### PowerPoint Templates
2123

22-
There are fewer customization options for PowerPoint than Word. Your template slide deck controls how the generated slide deck looks, but the content will always be the same.
24+
There are fewer customization options for PowerPoint than Word. Your template slide deck controls how the generated slide deck looks, but the content
25+
will always be the same.
2326

2427
<Warning>
25-
The PowerPoint template should be empty – i.e., should contain zero slides. Edit the master slides to control colors, layout, slide numbers, and other details.
28+
The PowerPoint template should be empty – i.e., should contain zero slides. Edit the master slides to control colors, layout, slide numbers, and other
29+
details.
2630

2731
To open your slide master view in PowerPoint: *View* » *Slide Master*
28-
</Warning>
29-
Ghostwriter will add slides to your chosen template slide deck. The new slides include placeholders and dynamic project information.
30-
31-
<Info>
32-
Each slide is set to fit text to the size of the text field. This auto-resizing happens in PowerPoint's rendering engine, so a slide's text might extend beyond the slide's lower boundary when you open your new presentation.
33-
34-
PowerPoint should activate auto-resizing when you save the presentation or edit any text.
35-
</Info>
36-
* **Title Slide**
3732

38-
* Includes your configured company name, selected project type, and client name
39-
40-
* **Agenda Slide**
33+
PowerPoint remembers if you closed the deck with the Slide Master view open or not and will re-open where you left off. To avoid every presentation opening
34+
on the view, close it before saving and uploading your template.
35+
</Warning>
4136

42-
* Placeholder for you to enter a meeting agenda
37+
Your templates should include at least three slide layouts in your _Slide Master_ view. Your first layout must be your title slide layout, the second layout
38+
must be your content layout, and your last layout must be your final/conclusion slide layout. Ghostwriter uses these three layouts, but you are welcome to
39+
include additional layouts between the content layout and the final slide layout. Just ensure the layout for your conclusion slide is last.
4340

44-
* **Introduction Slide**
41+
Ghostwriter will attempt to intelligently use the placeholders you include in your layouts. The slide's title (type 1 placeholder) will go into the slide's
42+
title placeholder. Likewise, when Ghostwriter adds a subtitle to the title slide, it will try to use a subtitle placeholder (type 4 placeholder), if present.
43+
Finally, you want a content placeholder (type 7 placeholder) for the slide's main body.
4544

46-
* Placeholder for any presenter introductions
45+
<Info>
46+
Adding text boxes and other objects does not create placeholders. These are "shapes" that will always be present on slides created with the layout. If the
47+
expected placeholders are not present, Ghostwriter will try to fallback to using detected shapes. If there are no shapes, the reporting engine will create
48+
new shapes. Ghostwriter tries to make new shapes in the same position where title and content are often placed, but it's a best guess on our part.
4749

48-
* **Methodology Slide**
50+
Make use of placeholders for the best output!
51+
</Info>
4952

50-
* Placeholder for reviewing testing methodology
53+
Title and subtitle are special placeholders. Each slide can contain one of each. You can verify you have these placeholders in a couple of different ways.
54+
You can see if you have a title placeholder by looking at your layout and verifying the _Title_ checkbox is checked under the _Slide Master_ section of the
55+
ribbon menu. It's a good idea to toggle it off and on to verify the placeholder you expect to be the title is really the title. If someone manually moved
56+
the title to repurpose it (e.g., for content or as a footer placeholder), that placeholder is still the title for that layout.
5157

52-
* **Attack Path Overview Slide**
58+
Subtitles are trickier because PowerPoint actually offers no way to create these from the _Insert Placeholder_ menu. There is also no checkbox to create one
59+
like there is for _Title_ or _Footers_. Many default PowerPoint layouts include them, which is one way to get them. You can also copy and paste a subtitle placeholder from another slide. Finally, you can use a macro to insert a subtitle placeholder.
5360

54-
* Placeholder for where you might discuss assessment narratives
61+
These two macros can be helpful for identifying the type of placeholder you have selected and inserting a subtitle placeholder on your title slide (for use
62+
or copy/pasting):
5563

56-
* **Findings Overview Slide**
64+
```vb
65+
Sub InsertSubtitlePlaceholder()
66+
Dim oSlideMaster As Master
67+
Dim oLayout As CustomLayout
68+
Dim oSubtitleShape As Shape
5769

58-
* Includes a two-column table showing all findings (full title and severity)
70+
' Reference the first slide master
71+
Set oSlideMaster = ActivePresentation.SlideMaster
5972

60-
* **Findings Slides**
73+
' Reference the first layout (or specific layout)
74+
Set oLayout = oSlideMaster.CustomLayouts(1)
6175

62-
* One slide per finding that includes:
76+
' Add a subtitle placeholder
77+
Set oSubtitleShape = oLayout.Shapes.AddPlaceholder(Type:=ppPlaceholderSubtitle, _
78+
Left:=100, Top:=300, Width:=500, Height:=100)
6379

64-
* Finding title as the slide title
80+
oSubtitleShape.TextFrame.TextRange.Text = "Subtitle Placeholder"
81+
End Sub
6582

66-
* All image evidence files as inserted images
83+
Sub ShowPlaceholderType()
84+
With ActiveWindow.Selection.ShapeRange
85+
If .Type = msoPlaceholder Then
86+
Select Case .PlaceholderFormat.Type
6787

68-
* All text evidence files as new text blocks 9styled with a fixed-width font)
88+
Case ppPlaceholderTitle
89+
MsgBox "Title Placeholder"
6990

70-
* Finding description as main slide content
91+
Case ppPlaceholderCenterTitle
92+
MsgBox "Centered Title Placeholder"
7193

72-
* All other finding information in the slide's notes field
94+
Case ppPlaceholderSubtitle
95+
MsgBox "Subtitle Placeholder"
7396

74-
* **Observations Slide**
97+
Case ppPlaceholderObject
98+
MsgBox "Content Placeholder"
7599

76-
* Placeholder for any additional observations
100+
End Select
101+
End If
102+
End With
103+
End Sub
104+
```
77105

78-
* **Recommendations Slide**
106+
For further reference: [PowerPoint PpPlaceholderType Enumeration](https://learn.microsoft.com/en-us/office/vba/api/powerpoint.ppplaceholdertype)
79107

80-
* Placeholder for any recommendations
108+
### PowerPoint Generation
81109

82-
* **Conclusion Slide**
110+
Ghostwriter will add slides to your chosen template slide deck. The new slides include placeholders and dynamic project information.
83111

84-
* Placeholder for closing statements or next steps
112+
<Info>
113+
Each slide is set to fit text to the size of the text field. This auto-resizing happens in PowerPoint's rendering engine, so a slide's text might extend
114+
beyond the slide's lower boundary when you open your new presentation.
85115

86-
* **Final Slide**
116+
PowerPoint should activate auto-resizing when you save the presentation or edit any text.
117+
</Info>
87118

88-
* Closing title slide that includes your configured company name, social media account, and email address
119+
| Slide Type | Description |
120+
|------------|-------------|
121+
| Title Slide | Includes your configured company name, selected project type, and client name |
122+
| Agenda Slide | Placeholder for you to enter a meeting agenda |
123+
| Introduction Slide | Placeholder for any presenter introductions |
124+
| Methodology Slide | Placeholder for reviewing testing methodology |
125+
| Attack Path Overview Slide | Placeholder for where you might discuss assessment narratives |
126+
| Findings Overview Slide | Includes a two-column table showing all findings (full title and severity) |
127+
| Findings Slides | One slide per finding that includes:<br/>• Finding title as the slide title<br/>• All image evidence files as inserted images<br/>• All text evidence files as new text blocks (styled with a fixed-width font)<br/>• Finding description as main slide content<br/>• All other finding information in the slide's notes field |
128+
| Observations Slide | Placeholder for any additional observations |
129+
| Recommendations Slide | Placeholder for any recommendations |
130+
| Conclusion Slide | Placeholder for closing statements or next steps |
131+
| Final Slide | Closing title slide that includes your configured company name, social media account, and email address |

VERSION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
v6.2.5
2-
3 March 2026
1+
v6.2.6
2+
5 March 2026

config/settings/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
# 3rd Party Libraries
1212
import environ
1313

14-
__version__ = "6.2.5"
14+
__version__ = "6.2.6"
1515
VERSION = __version__
16-
RELEASE_DATE = "3 March 2026"
16+
RELEASE_DATE = "5 March 2026"
1717

1818
ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
1919
APPS_DIR = ROOT_DIR / "ghostwriter"

ghostwriter/home/admin.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
11
"""This contains customizations for displaying the Home application models in the admin panel."""
22

3+
# Standard Library Imports
4+
import os
5+
36
# Django Imports
7+
from django.conf import settings
48
from django.contrib import admin
9+
from django.urls import reverse
10+
from django.utils.html import format_html
511

612
# Ghostwriter Libraries
713
from ghostwriter.home.models import UserProfile
814

915

1016
@admin.register(UserProfile)
1117
class UserProfileAdmin(admin.ModelAdmin):
12-
list_display = ("user", "avatar")
18+
list_display = ("user",)
1319
list_filter = ("user",)
20+
readonly_fields = ("avatar_download_link",)
21+
search_fields = ("user__username", "user__email")
22+
23+
class Media:
24+
js = ('js/admin/userprofile_admin.js',)
25+
26+
def avatar_download_link(self, obj):
27+
"""Display a download link in the detail view."""
28+
try:
29+
file_path = obj.avatar.path
30+
except ValueError:
31+
file_path = os.path.join(settings.STATICFILES_DIRS[0], "images/default_avatar.png")
32+
33+
if os.path.exists(file_path) and obj.avatar and obj.id:
34+
filename = os.path.basename(obj.avatar.name)
35+
return format_html(
36+
'<a href="{url}" download="{filename}">{filename}</a>',
37+
url=reverse("users:avatar_download", args=[obj.user.username]),
38+
filename=filename
39+
)
40+
return "File missing or not available for download"
41+
avatar_download_link.short_description = "Download File"

ghostwriter/home/models.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# Django Imports
77
from django.conf import settings
88
from django.db import models
9-
from django.templatetags.static import static
109

1110

1211
# Create your models here.
@@ -29,13 +28,3 @@ class Meta:
2928
ordering = ["user"]
3029
verbose_name = "User profile"
3130
verbose_name_plural = "User profiles"
32-
33-
@property
34-
def avatar_url(self):
35-
try:
36-
# Only return the image URL if the file is present
37-
if os.path.exists(self.avatar.path):
38-
return self.avatar.url
39-
return static("images/default_avatar.png")
40-
except ValueError:
41-
return static("images/default_avatar.png")

ghostwriter/home/tests/test_models.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -62,26 +62,3 @@ def test_crud_finding(self):
6262
# Delete
6363
user.delete()
6464
self.assertFalse(UserProfile.objects.all().exists())
65-
66-
def test_avatar_url_property(self):
67-
user = UserFactory()
68-
profile = UserProfile.objects.get(user=user)
69-
try:
70-
# Test default avatar file is returned
71-
url = profile.avatar_url
72-
self.assertIn("images/default_avatar.png", url)
73-
74-
# Set avatar file and confirm URL changes
75-
profile.avatar = self.uploaded_image_file
76-
profile.save()
77-
profile.refresh_from_db()
78-
url = profile.avatar_url
79-
self.assertIn(f"images/user_avatars/{user.id}/fake.png", url)
80-
81-
# Delete the avatar file and confirm default avatar is returned
82-
os.remove(profile.avatar.path)
83-
profile.refresh_from_db()
84-
url = profile.avatar_url
85-
self.assertIn("images/default_avatar.png", url)
86-
except Exception:
87-
self.fail("UserProfile model `avatar_url` property failed unexpectedly!")

ghostwriter/modules/reportwriter/base/pptx.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ def lint(cls, template_loc: str) -> Tuple[List[str], List[str]]:
162162
# Slide styles (From Master Style counting top to bottom from 0..n)
163163
SLD_LAYOUT_TITLE = 0
164164
SLD_LAYOUT_TITLE_AND_CONTENT = 1
165-
SLD_LAYOUT_FINAL = 12
166165

167166

168167
def add_slide_number(txtbox):

0 commit comments

Comments
 (0)