From 00645f50b6d8fbd23e683384a0d617e9b122541c Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 24 Mar 2026 20:23:55 +0100 Subject: [PATCH] fix(templates): reuse ProjectDuplicate.Create for template creation and fix is_template filter semantics --- pkg/models/project_duplicate.go | 12 ++++++++- pkg/models/project_template.go | 45 +++++---------------------------- pkg/webtests/project_test.go | 4 +-- 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/pkg/models/project_duplicate.go b/pkg/models/project_duplicate.go index dadfebe29..ede5841a9 100644 --- a/pkg/models/project_duplicate.go +++ b/pkg/models/project_duplicate.go @@ -42,6 +42,8 @@ type ProjectDuplicate struct { SkipPermissions bool `json:"-"` // If true, skip copying task assignees and comments SkipAssigneesAndComments bool `json:"-"` + // If true, the duplicated project will be marked as a template + IsTemplate bool `json:"-"` web.Permissions `json:"-"` web.CRUDable `json:"-"` @@ -90,7 +92,15 @@ func (pd *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) { pd.Project.ParentProjectID = pd.ParentProjectID // Set the owner to the current user pd.Project.OwnerID = doer.GetID() - pd.Project.Title += " - duplicate" + + if pd.IsTemplate { + pd.Project.IsTemplate = true + pd.Project.ParentProjectID = 0 + pd.SkipPermissions = true + pd.SkipAssigneesAndComments = true + } else { + pd.Project.Title += " - duplicate" + } err = CreateProject(s, pd.Project, doer, false, false) if err != nil { // If there is no available unique project identifier, just reset it. diff --git a/pkg/models/project_template.go b/pkg/models/project_template.go index 5af4751e2..20e582bca 100644 --- a/pkg/models/project_template.go +++ b/pkg/models/project_template.go @@ -35,7 +35,7 @@ type ProjectTemplate struct { web.CRUDable `json:"-"` } -// CanCreate checks if a user has the right to create a template from a project +// CanCreate checks if a user has the permission to create a template from a project func (pt *ProjectTemplate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bool, err error) { p := &Project{ID: pt.ProjectID} canCreate, _, err = p.CanRead(s, a) @@ -57,56 +57,23 @@ func (pt *ProjectTemplate) CanCreate(s *xorm.Session, a web.Auth) (canCreate boo func (pt *ProjectTemplate) Create(s *xorm.Session, doer web.Auth) (err error) { log.Debugf("Creating template from project %d", pt.ProjectID) - // Use ProjectDuplicate to copy the project pd := &ProjectDuplicate{ - ProjectID: pt.ProjectID, - SkipPermissions: true, - SkipAssigneesAndComments: true, + ProjectID: pt.ProjectID, + IsTemplate: true, } - // Read the source project + // Read the source project so the duplicate has something to work with pd.Project = &Project{ID: pt.ProjectID} err = pd.Project.ReadOne(s, doer) if err != nil { return err } - // Reset and mark as template - pd.Project.ID = 0 - pd.Project.Identifier = "" - pd.Project.ParentProjectID = 0 - pd.Project.OwnerID = doer.GetID() - pd.Project.IsTemplate = true - - err = CreateProject(s, pd.Project, doer, false, false) + err = pd.Create(s, doer) if err != nil { - if IsErrProjectIdentifierIsNotUnique(err) { - pd.Project.Identifier = "" - err = CreateProject(s, pd.Project, doer, false, false) - } - if err != nil { - return err - } - } - - log.Debugf("Created template project %d from project %d", pd.Project.ID, pt.ProjectID) - - newTaskIDs, err := duplicateTasks(s, doer, pd) - if err != nil { - return - } - - err = duplicateViews(s, pd, doer, newTaskIDs) - if err != nil { - return - } - - err = duplicateProjectBackground(s, pd, doer) - if err != nil { - return + return err } pt.Project = pd.Project - err = pt.Project.ReadOne(s, doer) return } diff --git a/pkg/webtests/project_test.go b/pkg/webtests/project_test.go index 88a27ccad..5cb032426 100644 --- a/pkg/webtests/project_test.go +++ b/pkg/webtests/project_test.go @@ -101,12 +101,12 @@ func TestProject(t *testing.T) { assert.NotContains(t, rec.Body.String(), `Template Project`) assert.NotContains(t, rec.Body.String(), `Shared Template`) }) - t.Run("Templates only", func(t *testing.T) { + t.Run("Including templates", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"is_template": []string{"true"}}, nil) require.NoError(t, err) assert.Contains(t, rec.Body.String(), `Template Project`) assert.Contains(t, rec.Body.String(), `Shared Template`) - assert.NotContains(t, rec.Body.String(), `"title":"Test1"`) + assert.Contains(t, rec.Body.String(), `Test1`) }) }) t.Run("ReadOne", func(t *testing.T) {