Implement motion editing
parent
335ce16547
commit
2b98712aa8
@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright 2022 CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package forms
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.cacert.org/cacert-boardvoting/internal/models"
|
||||
"git.cacert.org/cacert-boardvoting/internal/validator"
|
||||
)
|
||||
|
||||
type NewMotionForm struct {
|
||||
Title string `form:"title"`
|
||||
Content string `form:"content"`
|
||||
Type *models.VoteType `form:"type"`
|
||||
Due int `form:"due"`
|
||||
validator.Validator `form:"-"`
|
||||
}
|
||||
|
||||
const (
|
||||
minimumTitleLength = 3
|
||||
maximumTitleLength = 200
|
||||
minimumContentLength = 3
|
||||
maximumContentLength = 8000
|
||||
|
||||
threeDays = 3
|
||||
oneWeek = 7
|
||||
twoWeeks = 14
|
||||
threeWeeks = 28
|
||||
)
|
||||
|
||||
func (f *NewMotionForm) Validate() {
|
||||
f.CheckField(
|
||||
validator.NotBlank(f.Title),
|
||||
"title",
|
||||
"This field cannot be blank",
|
||||
)
|
||||
f.CheckField(
|
||||
validator.MinChars(f.Title, minimumTitleLength),
|
||||
"title",
|
||||
fmt.Sprintf("This field must be at least %d characters long", minimumTitleLength),
|
||||
)
|
||||
f.CheckField(
|
||||
validator.MaxChars(f.Title, maximumTitleLength),
|
||||
"title",
|
||||
fmt.Sprintf("This field must be at most %d characters long", maximumTitleLength),
|
||||
)
|
||||
f.CheckField(
|
||||
validator.NotBlank(f.Content),
|
||||
"content",
|
||||
"This field cannot be blank",
|
||||
)
|
||||
f.CheckField(
|
||||
validator.MinChars(f.Content, minimumContentLength),
|
||||
"content",
|
||||
fmt.Sprintf("This field must be at least %d characters long", minimumContentLength),
|
||||
)
|
||||
f.CheckField(
|
||||
validator.MaxChars(f.Content, maximumContentLength),
|
||||
"content",
|
||||
fmt.Sprintf("This field must be at most %d characters long", maximumContentLength),
|
||||
)
|
||||
|
||||
f.CheckField(validator.PermittedInt(
|
||||
f.Due, threeDays, oneWeek, twoWeeks, threeWeeks), "due", "invalid duration choice",
|
||||
)
|
||||
}
|
||||
|
||||
type EditMotionForm struct {
|
||||
Title string `form:"title"`
|
||||
Content string `form:"content"`
|
||||
Type *models.VoteType `form:"type"`
|
||||
Due int `form:"due"`
|
||||
validator.Validator `form:"-"`
|
||||
}
|
||||
|
||||
func (f EditMotionForm) Validate() {
|
||||
f.CheckField(
|
||||
validator.NotBlank(f.Title),
|
||||
"title",
|
||||
"This field cannot be blank",
|
||||
)
|
||||
f.CheckField(
|
||||
validator.MinChars(f.Title, minimumTitleLength),
|
||||
"title",
|
||||
fmt.Sprintf("This field must be at least %d characters long", minimumTitleLength),
|
||||
)
|
||||
f.CheckField(
|
||||
validator.MaxChars(f.Title, maximumTitleLength),
|
||||
"title",
|
||||
fmt.Sprintf("This field must be at most %d characters long", maximumTitleLength),
|
||||
)
|
||||
f.CheckField(
|
||||
validator.NotBlank(f.Content),
|
||||
"content",
|
||||
"This field cannot be blank",
|
||||
)
|
||||
f.CheckField(
|
||||
validator.MinChars(f.Content, minimumContentLength),
|
||||
"content",
|
||||
fmt.Sprintf("This field must be at least %d characters long", minimumContentLength),
|
||||
)
|
||||
f.CheckField(
|
||||
validator.MaxChars(f.Content, maximumContentLength),
|
||||
"content",
|
||||
fmt.Sprintf("This field must be at most %d characters long", maximumContentLength),
|
||||
)
|
||||
|
||||
f.CheckField(validator.PermittedInt(
|
||||
f.Due, threeDays, oneWeek, twoWeeks, threeWeeks), "due", "invalid duration choice",
|
||||
)
|
||||
}
|
@ -1,26 +1,26 @@
|
||||
Dear Board,
|
||||
|
||||
{{ .Name }} has modified motion {{ .Tag }} to the following:
|
||||
{{ .Data.Name }} has modified motion {{ .Data.Tag }} to the following:
|
||||
|
||||
{{ .Title }}
|
||||
{{ .Data.Title }}
|
||||
|
||||
{{ wrap 76 .Content }}
|
||||
{{ wrap 76 .Data.Content }}
|
||||
|
||||
Vote type: {{ .VoteType }}
|
||||
Vote type: {{ .Data.Type }}
|
||||
|
||||
Voting will close {{ .Due }}
|
||||
Voting will close {{ .Data.Due }}
|
||||
|
||||
To vote please choose:
|
||||
|
||||
Aye: {{ .VoteURL }}/aye
|
||||
Naye: {{ .VoteURL }}/naye
|
||||
Abstain: {{ .VoteURL }}/abstain
|
||||
Aye: {{ .BaseURL }}{{ .Data.VoteURL }}/aye
|
||||
Naye: {{ .BaseURL }}{{ .Data.VoteURL }}/naye
|
||||
Abstain: {{ .BaseURL }}{{ .Data.VoteURL }}/abstain
|
||||
|
||||
Please be aware, that if you have voted already your vote is still
|
||||
registered and valid. If this modification has an impact on how you wish to
|
||||
vote, you are responsible for voting again.
|
||||
|
||||
To see all your pending votes: {{ .UnvotedURL }}
|
||||
To see all your pending votes: {{ .BaseURL }}{{ .Data.UnvotedURL }}
|
||||
|
||||
Kind regards,
|
||||
the voting system
|
@ -1,72 +1,70 @@
|
||||
{{ define "title" }}New motion{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<div class="ui raised segment">
|
||||
<form action="/newmotion/" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||
<div class="ui form{{ if .Form.FieldErrors }} error{{ end }}">
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>ID:</label>
|
||||
(generated on submit)
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Proponent:</label>
|
||||
{{ .Form.User.Name }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Proposed date/time:</label>
|
||||
(auto filled to current date/time)
|
||||
</div>
|
||||
<form action="/newmotion/" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||
<div class="ui form segment{{ if .Form.FieldErrors }} error{{ end }}">
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>ID:</label>
|
||||
(generated on submit)
|
||||
</div>
|
||||
<div class="required field{{ if .Form.FieldErrors.title }} error{{ end }}">
|
||||
<label for="title">Title:</label>
|
||||
<input id="title" name="title" type="text" value="{{ .Form.Title }}">
|
||||
{{ if .Form.FieldErrors.title }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.title }}</span>
|
||||
{{ end }}
|
||||
<div class="field">
|
||||
<label>Proponent:</label>
|
||||
{{ .User.Name }}
|
||||
</div>
|
||||
<div class="required field{{ if .Form.FieldErrors.content }} error{{ end }}">
|
||||
<label for="content">Text:</label>
|
||||
<textarea id="content" name="content">{{ .Form.Content }}</textarea>
|
||||
{{ if .Form.FieldErrors.content }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.content }}</span>
|
||||
{{ end }}
|
||||
<div class="field">
|
||||
<label>Proposed date/time:</label>
|
||||
(auto filled to current date/time)
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="required field{{ if .Form.FieldErrors.type }} error{{ end }}">
|
||||
<label for="type">Vote type:</label>
|
||||
{{ $voteType := toString .Form.Type }}
|
||||
<select id="type" name="type">
|
||||
<option value="motion"
|
||||
{{ if eq "motion" $voteType }}selected{{ end }}>
|
||||
Motion
|
||||
</option>
|
||||
<option value="veto"
|
||||
{{ if eq "veto" $voteType }}selected{{ end }}>
|
||||
Veto
|
||||
</option>
|
||||
</select>
|
||||
{{ if .Form.FieldErrors.type }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.type}}</span>
|
||||
{{ end}}
|
||||
</div>
|
||||
<div class="required field{{ if .Form.FieldErrors.due }} error{{ end }}">
|
||||
<label for="due">Due: (autofilled from chosen
|
||||
option)</label>
|
||||
<select id="due" name="due">
|
||||
<option value="3"{{ if eq 3 .Form.Due }} selected{{ end }}>In 3 Days</option>
|
||||
<option value="7"{{ if eq 7 .Form.Due }} selected{{ end }}>In 1 Week</option>
|
||||
<option value="14"{{ if eq 14 .Form.Due }} selected{{ end }}>In 2 Weeks</option>
|
||||
<option value="28"{{ if eq 28 .Form.Due }} selected{{ end }}>In 4 Weeks</option>
|
||||
</select>
|
||||
{{ if .Form.FieldErrors.due }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.due }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="required field{{ if .Form.FieldErrors.title }} error{{ end }}">
|
||||
<label for="title">Title:</label>
|
||||
<input id="title" name="title" type="text" value="{{ .Form.Title }}">
|
||||
{{ if .Form.FieldErrors.title }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.title }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="required field{{ if .Form.FieldErrors.content }} error{{ end }}">
|
||||
<label for="content">Text:</label>
|
||||
<textarea id="content" name="content">{{ .Form.Content }}</textarea>
|
||||
{{ if .Form.FieldErrors.content }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.content }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="required field{{ if .Form.FieldErrors.type }} error{{ end }}">
|
||||
<label for="type">Vote type:</label>
|
||||
{{ $voteType := toString .Form.Type }}
|
||||
<select id="type" name="type">
|
||||
<option value="motion"
|
||||
{{ if eq "motion" $voteType }}selected{{ end }}>
|
||||
Motion
|
||||
</option>
|
||||
<option value="veto"
|
||||
{{ if eq "veto" $voteType }}selected{{ end }}>
|
||||
Veto
|
||||
</option>
|
||||
</select>
|
||||
{{ if .Form.FieldErrors.type }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.type}}</span>
|
||||
{{ end}}
|
||||
</div>
|
||||
<div class="required field{{ if .Form.FieldErrors.due }} error{{ end }}">
|
||||
<label for="due">Due: (autofilled from chosen
|
||||
option)</label>
|
||||
<select id="due" name="due">
|
||||
<option value="3"{{ if eq 3 .Form.Due }} selected{{ end }}>In 3 Days</option>
|
||||
<option value="7"{{ if eq 7 .Form.Due }} selected{{ end }}>In 1 Week</option>
|
||||
<option value="14"{{ if eq 14 .Form.Due }} selected{{ end }}>In 2 Weeks</option>
|
||||
<option value="28"{{ if eq 28 .Form.Due }} selected{{ end }}>In 4 Weeks</option>
|
||||
</select>
|
||||
{{ if .Form.FieldErrors.due }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.due }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<button class="ui button" type="submit">Propose</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<button class="ui button" type="submit">Propose</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
@ -0,0 +1,70 @@
|
||||
{{ define "title" }}Edit motion {{ .Motion.Tag }}{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<form action="/motions/{{ .Motion.Tag }}/edit" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||
<div class="ui form segment{{ if .Form.FieldErrors }} error{{ end }}">
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>ID:</label>
|
||||
{{ .Motion.Tag }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Proponent:</label>
|
||||
{{ .User.Name }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Proposed date/time:</label>
|
||||
{{ .Motion.Proposed }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="required field{{ if .Form.FieldErrors.title }} error{{ end }}">
|
||||
<label for="title">Title:</label>
|
||||
<input id="title" name="title" type="text" value="{{ .Form.Title }}">
|
||||
{{ if .Form.FieldErrors.title }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.title }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="required field{{ if .Form.FieldErrors.content }} error{{ end }}">
|
||||
<label for="content">Text:</label>
|
||||
<textarea id="content" name="content">{{ .Form.Content }}</textarea>
|
||||
{{ if .Form.FieldErrors.content }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.content }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="required field{{ if .Form.FieldErrors.type }} error{{ end }}">
|
||||
<label for="type">Vote type:</label>
|
||||
{{ $voteType := toString .Form.Type }}
|
||||
<select id="type" name="type">
|
||||
<option value="motion"
|
||||
{{ if eq "motion" $voteType }}selected{{ end }}>
|
||||
Motion
|
||||
</option>
|
||||
<option value="veto"
|
||||
{{ if eq "veto" $voteType }}selected{{ end }}>
|
||||
Veto
|
||||
</option>
|
||||
</select>
|
||||
{{ if .Form.FieldErrors.type }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.type}}</span>
|
||||
{{ end}}
|
||||
</div>
|
||||
<div class="required field{{ if .Form.FieldErrors.due }} error{{ end }}">
|
||||
<label for="due">Due: (autofilled from chosen
|
||||
option)</label>
|
||||
<select id="due" name="due">
|
||||
<option value="3"{{ if eq 3 .Form.Due }} selected{{ end }}>In 3 Days</option>
|
||||
<option value="7"{{ if eq 7 .Form.Due }} selected{{ end }}>In 1 Week</option>
|
||||
<option value="14"{{ if eq 14 .Form.Due }} selected{{ end }}>In 2 Weeks</option>
|
||||
<option value="28"{{ if eq 28 .Form.Due }} selected{{ end }}>In 4 Weeks</option>
|
||||
</select>
|
||||
{{ if .Form.FieldErrors.due }}
|
||||
<span class="ui small error text">{{ .Form.FieldErrors.due }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui button" type="submit">Modify</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
Loading…
Reference in New Issue