From 1a8928c4ae9edc702caff864b4b8473cf4d0d022 Mon Sep 17 00:00:00 2001 From: Forge Date: Thu, 19 Mar 2026 04:45:17 +0000 Subject: [PATCH 1/7] Add custom post type logo upload in create flow --- CHANGELOG.md | 1 + core/forms.py | 24 ++++- .../0065_projectcustomposttype_logo.py | 18 ++++ core/models.py | 17 +++- core/tests/test_custom_post_types.py | 94 +++++++++++++++++++ core/views.py | 4 +- .../components/project_navigation.html | 10 +- .../project_custom_post_type_posts.html | 13 ++- .../project/project_custom_post_types.html | 27 +++++- 9 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 core/migrations/0065_projectcustomposttype_logo.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d7a578b..67fc218 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - custom post types per project with validated name + prompt guidance, CRUD management UI, and Posts navigation integration - custom post types can be selected in navigation and applied as generation guidance for title suggestions - custom post-type guidance now also propagates into full article generation, with regression coverage for both title and content generation paths + - custom post type create/edit flow now supports optional logo uploads (PNG/JPG/WEBP/GIF up to 2MB), persists logos on the type record, renders logos across post-type UI surfaces, and falls back to default icons when no logo is set - Emails - Feedback email (for profiles with one product) - create project reminder for signed up users without project diff --git a/core/forms.py b/core/forms.py index 135a910..b618f0d 100644 --- a/core/forms.py +++ b/core/forms.py @@ -339,7 +339,7 @@ def clean_body(self): class ProjectCustomPostTypeForm(forms.ModelForm): class Meta: model = ProjectCustomPostType - fields = ["name", "prompt_guidance"] + fields = ["name", "prompt_guidance", "logo"] widgets = { "name": forms.TextInput( attrs={ @@ -356,6 +356,12 @@ class Meta: "maxlength": "1200", } ), + "logo": forms.FileInput( + attrs={ + "class": "block w-full text-sm text-gray-900 rounded-md border border-gray-300 cursor-pointer bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-gray-500", + "accept": "image/png,image/jpeg,image/webp,image/gif", + } + ), } def clean_name(self): @@ -363,3 +369,19 @@ def clean_name(self): def clean_prompt_guidance(self): return (self.cleaned_data.get("prompt_guidance") or "").strip() + + def clean_logo(self): + logo = self.cleaned_data.get("logo") + if not logo: + return logo + + content_type = getattr(logo, "content_type", "") + if content_type not in ProjectCustomPostType.logo_allowed_content_types: + raise forms.ValidationError( + "Unsupported logo format. Use PNG, JPG, WEBP, or GIF." + ) + + if logo.size > ProjectCustomPostType.logo_max_file_size_bytes: + raise forms.ValidationError("Logo must be 2MB or smaller.") + + return logo diff --git a/core/migrations/0065_projectcustomposttype_logo.py b/core/migrations/0065_projectcustomposttype_logo.py new file mode 100644 index 0000000..f802a11 --- /dev/null +++ b/core/migrations/0065_projectcustomposttype_logo.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.8 on 2026-03-19 04:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0064_projectpageanalysisrun_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='projectcustomposttype', + name='logo', + field=models.ImageField(blank=True, upload_to='custom_post_type_logos/'), + ), + ] diff --git a/core/models.py b/core/models.py index e4bb5d8..2a8231b 100644 --- a/core/models.py +++ b/core/models.py @@ -10,7 +10,7 @@ from django.contrib.auth.models import User from django.core.exceptions import ValidationError from django.core.files.base import ContentFile -from django.core.validators import MaxLengthValidator +from django.core.validators import FileExtensionValidator, MaxLengthValidator from django.db import models, transaction from django.urls import reverse from django.utils import timezone @@ -769,6 +769,9 @@ class Meta: class ProjectCustomPostType(BaseModel): + logo_max_file_size_bytes = 2 * 1024 * 1024 + logo_allowed_content_types = {"image/png", "image/jpeg", "image/webp", "image/gif"} + project = models.ForeignKey( Project, on_delete=models.CASCADE, @@ -777,6 +780,11 @@ class ProjectCustomPostType(BaseModel): name = models.CharField(max_length=80) normalized_name = models.CharField(max_length=80, editable=False) prompt_guidance = models.TextField(validators=[MaxLengthValidator(1200)]) + logo = models.ImageField( + upload_to="custom_post_type_logos/", + blank=True, + validators=[FileExtensionValidator(allowed_extensions=["png", "jpg", "jpeg", "webp", "gif"])], + ) class Meta: ordering = ["name"] @@ -818,6 +826,13 @@ def clean(self): self.prompt_guidance = prompt_guidance + if self.logo: + logo_file_size = getattr(self.logo, "size", 0) or 0 + if logo_file_size > self.logo_max_file_size_bytes: + raise ValidationError({ + "logo": "Logo must be 2MB or smaller.", + }) + def save(self, *args, **kwargs): self.full_clean() return super().save(*args, **kwargs) diff --git a/core/tests/test_custom_post_types.py b/core/tests/test_custom_post_types.py index 3d530b0..a6cdfdc 100644 --- a/core/tests/test_custom_post_types.py +++ b/core/tests/test_custom_post_types.py @@ -3,6 +3,8 @@ import pytest from django.contrib.auth.models import User from django.core.exceptions import ValidationError +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import override_settings from django.urls import reverse from core.api.schemas import GenerateTitleSuggestionsIn @@ -11,6 +13,15 @@ from core.models import BlogPostTitleSuggestion, Project, ProjectCustomPostType +TINY_PNG_BYTES = ( + b"\x89PNG\r\n\x1a\n" + b"\x00\x00\x00\rIHDR" + b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89" + b"\x00\x00\x00\x0cIDATx\x9cc\xf8\xff\xff?\x00\x05\xfe\x02\xfeA\xdd\x98\x9f" + b"\x00\x00\x00\x00IEND\xaeB`\x82" +) + + @pytest.mark.django_db def test_custom_post_type_name_is_unique_per_project_case_insensitive(): user = User.objects.create_user("owner-custom-type", "owner-custom@example.com", "secret") @@ -230,3 +241,86 @@ def test_build_content_generation_prompt_does_not_duplicate_custom_post_type_gui generation_prompt = suggestion.build_content_generation_prompt() assert post_type.prompt_guidance not in generation_prompt + + +@pytest.mark.django_db +@override_settings(MEDIA_ROOT="/tmp/tuxseo-test-media") +def test_create_custom_post_type_with_logo_upload(client): + user = User.objects.create_user("owner-logo", "owner-logo@example.com", "secret") + project = Project.objects.create(profile=user.profile, name="Site", url="https://site.test") + + client.force_login(user) + response = client.post( + reverse("project_custom_post_types", kwargs={"pk": project.id}), + { + "name": "Case Study", + "prompt_guidance": "Use concrete outcomes and metrics.", + "logo": SimpleUploadedFile("logo.png", TINY_PNG_BYTES, content_type="image/png"), + }, + ) + + assert response.status_code == 302 + created_type = ProjectCustomPostType.objects.get(project=project, name="Case Study") + assert created_type.logo.name.startswith("custom_post_type_logos/") + + +@pytest.mark.django_db +@override_settings(MEDIA_ROOT="/tmp/tuxseo-test-media") +def test_create_custom_post_type_without_logo_works(client): + user = User.objects.create_user("owner-no-logo", "owner-no-logo@example.com", "secret") + project = Project.objects.create(profile=user.profile, name="Site", url="https://site.test") + + client.force_login(user) + response = client.post( + reverse("project_custom_post_types", kwargs={"pk": project.id}), + { + "name": "Roundup", + "prompt_guidance": "Summarize notable weekly updates.", + }, + ) + + assert response.status_code == 302 + created_type = ProjectCustomPostType.objects.get(project=project, name="Roundup") + assert not created_type.logo + + +@pytest.mark.django_db +@override_settings(MEDIA_ROOT="/tmp/tuxseo-test-media") +def test_create_custom_post_type_rejects_unsupported_logo_type(client): + user = User.objects.create_user("owner-bad-logo", "owner-bad-logo@example.com", "secret") + project = Project.objects.create(profile=user.profile, name="Site", url="https://site.test") + + client.force_login(user) + response = client.post( + reverse("project_custom_post_types", kwargs={"pk": project.id}), + { + "name": "Tutorial", + "prompt_guidance": "Instructional tone.", + "logo": SimpleUploadedFile("logo.txt", b"not-image", content_type="text/plain"), + }, + ) + + assert response.status_code == 200 + assert "Unsupported logo format" in response.content.decode("utf-8") + + +@pytest.mark.django_db +@override_settings(MEDIA_ROOT="/tmp/tuxseo-test-media") +def test_create_custom_post_type_rejects_oversized_logo(client): + user = User.objects.create_user("owner-big-logo", "owner-big-logo@example.com", "secret") + project = Project.objects.create(profile=user.profile, name="Site", url="https://site.test") + + oversized = b"0" * (ProjectCustomPostType.logo_max_file_size_bytes + 1) + + client.force_login(user) + response = client.post( + reverse("project_custom_post_types", kwargs={"pk": project.id}), + { + "name": "News", + "prompt_guidance": "Fast-paced updates.", + "logo": SimpleUploadedFile("logo.png", oversized, content_type="image/png"), + }, + ) + + assert response.status_code == 200 + assert "Logo must be 2MB or smaller" in response.content.decode("utf-8") diff --git a/core/views.py b/core/views.py index e81dd89..f8bb266 100644 --- a/core/views.py +++ b/core/views.py @@ -1520,7 +1520,7 @@ def get_context_data(self, **kwargs): def post(self, request, *args, **kwargs): self.object = self.get_object() - create_form = ProjectCustomPostTypeForm(request.POST) + create_form = ProjectCustomPostTypeForm(request.POST, request.FILES) if create_form.is_valid(): custom_post_type = create_form.save(commit=False) @@ -1549,7 +1549,7 @@ def post(self, request, pk, post_type_pk): project = get_object_or_404(Project, pk=pk, profile=request.user.profile) custom_post_type = get_object_or_404(ProjectCustomPostType, pk=post_type_pk, project=project) - form = ProjectCustomPostTypeForm(request.POST, instance=custom_post_type) + form = ProjectCustomPostTypeForm(request.POST, request.FILES, instance=custom_post_type) if form.is_valid(): try: form.save() diff --git a/frontend/templates/components/project_navigation.html b/frontend/templates/components/project_navigation.html index 352d8ad..b8c83ed 100644 --- a/frontend/templates/components/project_navigation.html +++ b/frontend/templates/components/project_navigation.html @@ -69,9 +69,13 @@

- - - + {% if post_type.logo %} + {{ post_type.name }} logo + {% else %} + + + + {% endif %} {{ post_type.name }} diff --git a/frontend/templates/project/project_custom_post_type_posts.html b/frontend/templates/project/project_custom_post_type_posts.html index 600dd96..18af08c 100644 --- a/frontend/templates/project/project_custom_post_type_posts.html +++ b/frontend/templates/project/project_custom_post_type_posts.html @@ -15,7 +15,18 @@ data-content-idea-post-type-id-value="{{ custom_post_type.id }}">
-

{{ custom_post_type.name }} Posts

+
+ {% if custom_post_type.logo %} + {{ custom_post_type.name }} logo + {% else %} +
+ + + +
+ {% endif %} +

{{ custom_post_type.name }} Posts

+

{{ custom_post_type.prompt_guidance }}

diff --git a/frontend/templates/project/project_custom_post_types.html b/frontend/templates/project/project_custom_post_types.html index 9a9e7fa..2608a68 100644 --- a/frontend/templates/project/project_custom_post_types.html +++ b/frontend/templates/project/project_custom_post_types.html @@ -11,7 +11,7 @@

Create custom post type

Define a reusable post style with prompt guidance used during idea generation.

-
+ {% csrf_token %}
@@ -23,6 +23,12 @@

Create custom post type

{{ create_form.prompt_guidance }} {% for error in create_form.prompt_guidance.errors %}

{{ error }}

{% endfor %}
+
+ + {{ create_form.logo }} +

Accepted: PNG, JPG, WEBP, GIF · max 2MB.

+ {% for error in create_form.logo.errors %}

{{ error }}

{% endfor %} +
{% for error in create_form.non_field_errors %}

{{ error }}

{% endfor %}
@@ -34,8 +40,20 @@

Existing custom post types

{% for post_type in custom_post_types %}
-
+ {% csrf_token %} +
+ {% if post_type.logo %} + {{ post_type.name }} logo + {% else %} +
+ + + +
+ {% endif %} +

{{ post_type.name }}

+
@@ -44,6 +62,11 @@

Existing custom post types

+
+ + +

Accepted: PNG, JPG, WEBP, GIF · max 2MB.

+
Open From 596f38680ab34aa4c93c06de0e3429c97f412d77 Mon Sep 17 00:00:00 2001 From: Forge Date: Thu, 19 Mar 2026 04:50:16 +0000 Subject: [PATCH 2/7] Fix custom post type logo form validation and tests --- core/forms.py | 16 ++++++++++------ core/tests/test_custom_post_types.py | 9 +++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/core/forms.py b/core/forms.py index b618f0d..0d793bc 100644 --- a/core/forms.py +++ b/core/forms.py @@ -337,6 +337,16 @@ def clean_body(self): class ProjectCustomPostTypeForm(forms.ModelForm): + logo = forms.FileField( + required=False, + widget=forms.FileInput( + attrs={ + "class": "block w-full text-sm text-gray-900 rounded-md border border-gray-300 cursor-pointer bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-gray-500", + "accept": "image/png,image/jpeg,image/webp,image/gif", + } + ), + ) + class Meta: model = ProjectCustomPostType fields = ["name", "prompt_guidance", "logo"] @@ -356,12 +366,6 @@ class Meta: "maxlength": "1200", } ), - "logo": forms.FileInput( - attrs={ - "class": "block w-full text-sm text-gray-900 rounded-md border border-gray-300 cursor-pointer bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-gray-500", - "accept": "image/png,image/jpeg,image/webp,image/gif", - } - ), } def clean_name(self): diff --git a/core/tests/test_custom_post_types.py b/core/tests/test_custom_post_types.py index a6cdfdc..f6425f8 100644 --- a/core/tests/test_custom_post_types.py +++ b/core/tests/test_custom_post_types.py @@ -1,3 +1,4 @@ +import base64 from types import SimpleNamespace import pytest @@ -13,12 +14,8 @@ from core.models import BlogPostTitleSuggestion, Project, ProjectCustomPostType -TINY_PNG_BYTES = ( - b"\x89PNG\r\n\x1a\n" - b"\x00\x00\x00\rIHDR" - b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89" - b"\x00\x00\x00\x0cIDATx\x9cc\xf8\xff\xff?\x00\x05\xfe\x02\xfeA\xdd\x98\x9f" - b"\x00\x00\x00\x00IEND\xaeB`\x82" +TINY_PNG_BYTES = base64.b64decode( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7+WZ0AAAAASUVORK5CYII=" ) From 70de995060dbdb2e7b1f942b9d76430a6c5dac69 Mon Sep 17 00:00:00 2001 From: Forge Date: Thu, 19 Mar 2026 04:51:47 +0000 Subject: [PATCH 3/7] Handle logo clearing and surface update validation errors --- core/forms.py | 15 ++++++++++ core/tests/test_custom_post_types.py | 30 +++++++++++++++++++ core/views.py | 7 +++++ .../project/project_custom_post_types.html | 6 ++++ 4 files changed, 58 insertions(+) diff --git a/core/forms.py b/core/forms.py index 0d793bc..7552bcc 100644 --- a/core/forms.py +++ b/core/forms.py @@ -346,6 +346,7 @@ class ProjectCustomPostTypeForm(forms.ModelForm): } ), ) + clear_logo = forms.BooleanField(required=False) class Meta: model = ProjectCustomPostType @@ -389,3 +390,17 @@ def clean_logo(self): raise forms.ValidationError("Logo must be 2MB or smaller.") return logo + + def save(self, commit=True): + instance = super().save(commit=False) + + if self.cleaned_data.get("clear_logo") and not self.cleaned_data.get("logo"): + if instance.logo: + instance.logo.delete(save=False) + instance.logo = None + + if commit: + instance.save() + self.save_m2m() + + return instance diff --git a/core/tests/test_custom_post_types.py b/core/tests/test_custom_post_types.py index f6425f8..94e7646 100644 --- a/core/tests/test_custom_post_types.py +++ b/core/tests/test_custom_post_types.py @@ -321,3 +321,33 @@ def test_create_custom_post_type_rejects_oversized_logo(client): assert response.status_code == 200 assert "Logo must be 2MB or smaller" in response.content.decode("utf-8") + + +@pytest.mark.django_db +@override_settings(MEDIA_ROOT="/tmp/tuxseo-test-media") +def test_update_custom_post_type_can_clear_existing_logo(client): + user = User.objects.create_user("owner-clear-logo", "owner-clear-logo@example.com", "secret") + project = Project.objects.create(profile=user.profile, name="Site", url="https://site.test") + post_type = ProjectCustomPostType.objects.create( + project=project, + name="Explainer", + prompt_guidance="Clear, concise explainers.", + logo=SimpleUploadedFile("logo.png", TINY_PNG_BYTES, content_type="image/png"), + ) + + client.force_login(user) + response = client.post( + reverse( + "project_custom_post_type_update", + kwargs={"pk": project.id, "post_type_pk": post_type.id}, + ), + { + "name": post_type.name, + "prompt_guidance": post_type.prompt_guidance, + "clear_logo": "1", + }, + ) + + assert response.status_code == 302 + post_type.refresh_from_db() + assert not post_type.logo diff --git a/core/views.py b/core/views.py index f8bb266..d22f6cd 100644 --- a/core/views.py +++ b/core/views.py @@ -1565,6 +1565,13 @@ def post(self, request, pk, post_type_pk): ) else: messages.error(request, "Could not update custom post type. Please check the form.") + for field_name, field_errors in form.errors.items(): + if field_name == "__all__": + label = "Form" + else: + label = form.fields[field_name].label or field_name.replace("_", " ").title() + for field_error in field_errors: + messages.error(request, f"{label}: {field_error}") return redirect("project_custom_post_types", pk=project.pk) diff --git a/frontend/templates/project/project_custom_post_types.html b/frontend/templates/project/project_custom_post_types.html index 2608a68..9609c11 100644 --- a/frontend/templates/project/project_custom_post_types.html +++ b/frontend/templates/project/project_custom_post_types.html @@ -66,6 +66,12 @@

Existing custom post types

Accepted: PNG, JPG, WEBP, GIF · max 2MB.

+ {% if post_type.logo %} + + {% endif %}
From 88ebecff80e353d28388e227d416f8c70e247862 Mon Sep 17 00:00:00 2001 From: Forge Date: Thu, 19 Mar 2026 04:56:52 +0000 Subject: [PATCH 4/7] Fix clear_logo handling when no new upload --- core/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/forms.py b/core/forms.py index 7552bcc..1a3a5c6 100644 --- a/core/forms.py +++ b/core/forms.py @@ -394,7 +394,7 @@ def clean_logo(self): def save(self, commit=True): instance = super().save(commit=False) - if self.cleaned_data.get("clear_logo") and not self.cleaned_data.get("logo"): + if self.cleaned_data.get("clear_logo") and not self.files.get("logo"): if instance.logo: instance.logo.delete(save=False) instance.logo = None From b2f33d12ccb410372599e9888f949ea982b99e49 Mon Sep 17 00:00:00 2001 From: Forge Date: Thu, 19 Mar 2026 05:02:01 +0000 Subject: [PATCH 5/7] Clear custom post type logo in update view --- core/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/views.py b/core/views.py index d22f6cd..11d6551 100644 --- a/core/views.py +++ b/core/views.py @@ -1552,7 +1552,12 @@ def post(self, request, pk, post_type_pk): form = ProjectCustomPostTypeForm(request.POST, request.FILES, instance=custom_post_type) if form.is_valid(): try: - form.save() + updated_post_type = form.save() + if request.POST.get("clear_logo") and not request.FILES.get("logo"): + if updated_post_type.logo: + updated_post_type.logo.delete(save=False) + updated_post_type.logo = None + updated_post_type.save(update_fields=["logo"]) messages.success(request, f"Updated custom post type '{custom_post_type.name}'.") except ValidationError as error: validation_messages = [] From 71f712a61dc7e81e327ab8c611d70259b0abd65f Mon Sep 17 00:00:00 2001 From: Forge Date: Thu, 19 Mar 2026 05:07:10 +0000 Subject: [PATCH 6/7] Store blank value when clearing post type logo --- core/forms.py | 2 +- core/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/forms.py b/core/forms.py index 1a3a5c6..03ed106 100644 --- a/core/forms.py +++ b/core/forms.py @@ -397,7 +397,7 @@ def save(self, commit=True): if self.cleaned_data.get("clear_logo") and not self.files.get("logo"): if instance.logo: instance.logo.delete(save=False) - instance.logo = None + instance.logo = "" if commit: instance.save() diff --git a/core/views.py b/core/views.py index 11d6551..a9ea103 100644 --- a/core/views.py +++ b/core/views.py @@ -1556,7 +1556,7 @@ def post(self, request, pk, post_type_pk): if request.POST.get("clear_logo") and not request.FILES.get("logo"): if updated_post_type.logo: updated_post_type.logo.delete(save=False) - updated_post_type.logo = None + updated_post_type.logo = "" updated_post_type.save(update_fields=["logo"]) messages.success(request, f"Updated custom post type '{custom_post_type.name}'.") except ValidationError as error: From d785d98c92a9b5a478de819628228f0d6f6059af Mon Sep 17 00:00:00 2001 From: Forge Date: Thu, 19 Mar 2026 05:13:06 +0000 Subject: [PATCH 7/7] Drop unstable clear-logo path; keep create-flow logo coverage --- core/forms.py | 14 --------- core/tests/test_custom_post_types.py | 30 ------------------- core/views.py | 7 +---- .../project/project_custom_post_types.html | 6 ---- 4 files changed, 1 insertion(+), 56 deletions(-) diff --git a/core/forms.py b/core/forms.py index 03ed106..77c0db3 100644 --- a/core/forms.py +++ b/core/forms.py @@ -346,7 +346,6 @@ class ProjectCustomPostTypeForm(forms.ModelForm): } ), ) - clear_logo = forms.BooleanField(required=False) class Meta: model = ProjectCustomPostType @@ -391,16 +390,3 @@ def clean_logo(self): return logo - def save(self, commit=True): - instance = super().save(commit=False) - - if self.cleaned_data.get("clear_logo") and not self.files.get("logo"): - if instance.logo: - instance.logo.delete(save=False) - instance.logo = "" - - if commit: - instance.save() - self.save_m2m() - - return instance diff --git a/core/tests/test_custom_post_types.py b/core/tests/test_custom_post_types.py index 94e7646..f6425f8 100644 --- a/core/tests/test_custom_post_types.py +++ b/core/tests/test_custom_post_types.py @@ -321,33 +321,3 @@ def test_create_custom_post_type_rejects_oversized_logo(client): assert response.status_code == 200 assert "Logo must be 2MB or smaller" in response.content.decode("utf-8") - - -@pytest.mark.django_db -@override_settings(MEDIA_ROOT="/tmp/tuxseo-test-media") -def test_update_custom_post_type_can_clear_existing_logo(client): - user = User.objects.create_user("owner-clear-logo", "owner-clear-logo@example.com", "secret") - project = Project.objects.create(profile=user.profile, name="Site", url="https://site.test") - post_type = ProjectCustomPostType.objects.create( - project=project, - name="Explainer", - prompt_guidance="Clear, concise explainers.", - logo=SimpleUploadedFile("logo.png", TINY_PNG_BYTES, content_type="image/png"), - ) - - client.force_login(user) - response = client.post( - reverse( - "project_custom_post_type_update", - kwargs={"pk": project.id, "post_type_pk": post_type.id}, - ), - { - "name": post_type.name, - "prompt_guidance": post_type.prompt_guidance, - "clear_logo": "1", - }, - ) - - assert response.status_code == 302 - post_type.refresh_from_db() - assert not post_type.logo diff --git a/core/views.py b/core/views.py index a9ea103..d22f6cd 100644 --- a/core/views.py +++ b/core/views.py @@ -1552,12 +1552,7 @@ def post(self, request, pk, post_type_pk): form = ProjectCustomPostTypeForm(request.POST, request.FILES, instance=custom_post_type) if form.is_valid(): try: - updated_post_type = form.save() - if request.POST.get("clear_logo") and not request.FILES.get("logo"): - if updated_post_type.logo: - updated_post_type.logo.delete(save=False) - updated_post_type.logo = "" - updated_post_type.save(update_fields=["logo"]) + form.save() messages.success(request, f"Updated custom post type '{custom_post_type.name}'.") except ValidationError as error: validation_messages = [] diff --git a/frontend/templates/project/project_custom_post_types.html b/frontend/templates/project/project_custom_post_types.html index 9609c11..2608a68 100644 --- a/frontend/templates/project/project_custom_post_types.html +++ b/frontend/templates/project/project_custom_post_types.html @@ -66,12 +66,6 @@

Existing custom post types

Accepted: PNG, JPG, WEBP, GIF · max 2MB.

- {% if post_type.logo %} - - {% endif %}