Implement editing labels
as requested in https://todo.sr.ht/~sircmpwn/todo.sr.ht/159
This commit is contained in:
parent
53cb5b596c
commit
7d018e76e1
|
@ -1,6 +1,7 @@
|
|||
from flask import Blueprint, render_template, request, url_for, abort, redirect
|
||||
from flask_login import current_user
|
||||
from todosrht import color
|
||||
from todosrht.color import color_from_hex, color_to_hex, get_text_color
|
||||
from todosrht.color import valid_hex_color_code
|
||||
from todosrht.access import get_tracker
|
||||
from todosrht.search import apply_search
|
||||
from todosrht.tickets import get_last_seen_times, get_comment_counts
|
||||
|
@ -411,6 +412,30 @@ def tracker_labels_GET(owner, name):
|
|||
return render_template("tracker-labels.html",
|
||||
tracker=tracker, access=access, is_owner=is_owner)
|
||||
|
||||
|
||||
def validate_label(request):
|
||||
valid = Validation(request)
|
||||
name = valid.require("name")
|
||||
color = valid.require("color")
|
||||
if not valid.ok:
|
||||
return None, valid
|
||||
|
||||
valid.expect(2 < len(name) < 50,
|
||||
"Must be between 2 and 50 characters", field="name")
|
||||
valid.expect(valid_hex_color_code(color),
|
||||
"Invalid hex color code", field="color")
|
||||
if not valid.ok:
|
||||
return None, valid
|
||||
|
||||
# Determine a foreground color to use
|
||||
color_rgb = color_from_hex(color)
|
||||
text_color_rgb = get_text_color(color_rgb)
|
||||
text_color = color_to_hex(text_color_rgb)
|
||||
|
||||
label = dict(name=name, color=color, text_color=text_color)
|
||||
return label, valid
|
||||
|
||||
|
||||
@tracker.route("/<owner>/<name>/labels", methods=["POST"])
|
||||
@loginrequired
|
||||
def tracker_labels_POST(owner, name):
|
||||
|
@ -421,26 +446,14 @@ def tracker_labels_POST(owner, name):
|
|||
if not is_owner:
|
||||
abort(403)
|
||||
|
||||
valid = Validation(request)
|
||||
label_name = valid.require("name")
|
||||
label_color = valid.require("color")
|
||||
data, valid = validate_label(request)
|
||||
if not valid.ok:
|
||||
return render_template("tracker-labels.html",
|
||||
tracker=tracker, access=access, is_owner=is_owner,
|
||||
**valid.kwargs), 400
|
||||
|
||||
valid.expect(2 < len(label_name) < 50,
|
||||
"Must be between 2 and 50 characters", field="name")
|
||||
valid.expect(color.valid_hex_color_code(label_color),
|
||||
"Invalid hex color code", field="color")
|
||||
if not valid.ok:
|
||||
return render_template("tracker-labels.html",
|
||||
tracker=tracker, access=access, is_owner=is_owner,
|
||||
**valid.kwargs), 400
|
||||
|
||||
existing_label = (Label.query
|
||||
.filter(Label.tracker_id == tracker.id)
|
||||
.filter(Label.name == label_name)).first()
|
||||
existing_label = Label.query.filter_by(
|
||||
tracker=tracker, name=data["name"]).one_or_none()
|
||||
valid.expect(not existing_label,
|
||||
"A label with this name already exists", field="name")
|
||||
if not valid.ok:
|
||||
|
@ -448,16 +461,7 @@ def tracker_labels_POST(owner, name):
|
|||
tracker=tracker, access=access, is_owner=is_owner,
|
||||
**valid.kwargs), 400
|
||||
|
||||
# Determine a foreground color to use
|
||||
label_color_rgb = color.color_from_hex(label_color)
|
||||
text_color_rgb = color.get_text_color(label_color_rgb)
|
||||
text_color = color.color_to_hex(text_color_rgb)
|
||||
|
||||
label = Label()
|
||||
label.tracker_id = tracker.id
|
||||
label.name = label_name
|
||||
label.color = label_color
|
||||
label.text_color = text_color
|
||||
label = Label(tracker=tracker, **data)
|
||||
db.session.add(label)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -466,6 +470,56 @@ def tracker_labels_POST(owner, name):
|
|||
TrackerWebhook.Subscription.tracker_id == tracker.id)
|
||||
return redirect(url_for(".tracker_labels_GET", owner=owner, name=name))
|
||||
|
||||
@tracker.route("/<owner>/<name>/labels/<label_name>/")
|
||||
@loginrequired
|
||||
def label_edit_GET(owner, name, label_name):
|
||||
tracker, access = get_tracker(owner, name)
|
||||
if not tracker:
|
||||
abort(404)
|
||||
if current_user.id != tracker.owner_id:
|
||||
abort(403)
|
||||
|
||||
label = Label.query.filter_by(tracker=tracker, name=label_name).first()
|
||||
if not label:
|
||||
abort(404)
|
||||
|
||||
return render_template("tracker-label-edit.html",
|
||||
tracker=tracker, access=access, label=label)
|
||||
|
||||
@tracker.route("/<owner>/<name>/labels/<label_name>/", methods=["POST"])
|
||||
@loginrequired
|
||||
def label_edit_POST(owner, name, label_name):
|
||||
tracker, access = get_tracker(owner, name)
|
||||
if not tracker:
|
||||
abort(404)
|
||||
if current_user.id != tracker.owner_id:
|
||||
abort(403)
|
||||
|
||||
label = Label.query.filter_by(
|
||||
tracker=tracker, name=label_name).one_or_none()
|
||||
if not label:
|
||||
abort(404)
|
||||
|
||||
data, valid = validate_label(request)
|
||||
if not valid.ok:
|
||||
return render_template("tracker-label-edit.html",
|
||||
tracker=tracker, access=access, label=label, **valid.kwargs), 400
|
||||
|
||||
existing_label = Label.query.filter_by(
|
||||
tracker=tracker, name=data["name"]).one_or_none()
|
||||
valid.expect(not existing_label or existing_label == label,
|
||||
"A label with this name already exists", field="name")
|
||||
if not valid.ok:
|
||||
return render_template("tracker-label-edit.html",
|
||||
tracker=tracker, access=access, **valid.kwargs), 400
|
||||
|
||||
label.name = data["name"]
|
||||
label.color = data["color"]
|
||||
label.text_color = data["text_color"]
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for(".tracker_labels_GET", owner=owner, name=name))
|
||||
|
||||
@tracker.route("/<owner>/<name>/labels/<int:label_id>/delete", methods=["POST"])
|
||||
@loginrequired
|
||||
def delete_label(owner, name, label_id):
|
||||
|
|
|
@ -28,6 +28,7 @@ class TodoApp(SrhtFlask):
|
|||
self.add_template_filter(filters.render_comment)
|
||||
self.add_template_filter(filters.render_ticket_description)
|
||||
self.add_template_filter(urls.label_add_url)
|
||||
self.add_template_filter(urls.label_edit_url)
|
||||
self.add_template_filter(urls.label_search_url)
|
||||
self.add_template_filter(urls.participant_url)
|
||||
self.add_template_filter(urls.ticket_assign_url)
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}
|
||||
<title>
|
||||
labels
|
||||
—
|
||||
{{tracker.name}}
|
||||
—
|
||||
{{ cfg("sr.ht", "site-name") }} todo
|
||||
</title>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
<div class="header-tabbed">
|
||||
<div class="container">
|
||||
<ul class="nav nav-tabs">
|
||||
<h2>
|
||||
<a href="{{ tracker.owner|user_url }}">{{ tracker.owner }}</a
|
||||
>/{{ tracker.name }}
|
||||
</h2>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ tracker|tracker_url }}">
|
||||
{{icon('caret-left')}} back
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active"
|
||||
href="{{ tracker|tracker_labels_url }}">labels</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<h3>Edit label</h3>
|
||||
<form method="POST">
|
||||
{{csrf_token()}}
|
||||
<div class="form-row">
|
||||
<div class="col-auto">
|
||||
<div class="form-group" style="width: 4rem">
|
||||
<label for="color">Color</label>
|
||||
<input
|
||||
type="color"
|
||||
class="form-control {{ valid.cls("color") }}"
|
||||
id="color"
|
||||
name="color"
|
||||
style="height: 2.2rem"
|
||||
value="{{ valid.kwargs.color or label.color }}" />
|
||||
{{valid.summary("color")}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control {{ valid.cls("name") }}"
|
||||
maxlength="2048"
|
||||
id="name"
|
||||
name="name"
|
||||
value="{{ valid.kwargs.name or label.name }}" />
|
||||
{{valid.summary("name")}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<a href="{{ url_for("tracker.tracker_labels_GET",
|
||||
owner=tracker.owner, name=tracker.name) }}" class="btn btn-default">
|
||||
Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Save {{icon("caret-right")}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -30,11 +30,48 @@
|
|||
</div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
{% if tracker.description %}
|
||||
{{ tracker.description | md }}
|
||||
{% endif %}
|
||||
{% if is_owner %}
|
||||
<form method="POST" class="col-md-6">
|
||||
<h3>Add label</h3>
|
||||
{{csrf_token()}}
|
||||
<div class="form-row">
|
||||
<div class="col-auto">
|
||||
<div class="form-group" style="width: 4rem">
|
||||
<label for="color">Color</label>
|
||||
<input
|
||||
type="color"
|
||||
class="form-control {{ valid.cls("color") }}"
|
||||
id="color"
|
||||
name="color"
|
||||
style="height: 2.2rem"
|
||||
value="{{ color or "#ffffff" }}" />
|
||||
{{valid.summary("color")}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control {{ valid.cls("name") }}"
|
||||
maxlength="2048"
|
||||
id="name"
|
||||
name="name"
|
||||
value="{{ name or "" }}" />
|
||||
{{valid.summary("name")}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Add label {{icon("caret-right")}}
|
||||
</button>
|
||||
</form>
|
||||
<div class="col-md-6">
|
||||
{% else %}
|
||||
<div class="col-md-12">
|
||||
{% endif %}
|
||||
{% if tracker.labels %}
|
||||
<h3>Labels</h3>
|
||||
<div class="label-list">
|
||||
{% for label in tracker.labels %}
|
||||
<div class="row">
|
||||
|
@ -47,6 +84,11 @@
|
|||
</a>
|
||||
</div>
|
||||
{% if is_owner %}
|
||||
<div class="col-auto">
|
||||
<a href="{{ label|label_edit_url }}" class="btn btn-link">
|
||||
Edit {{ icon("caret-right") }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<form
|
||||
method="post"
|
||||
|
@ -72,44 +114,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if is_owner %}
|
||||
<div class="container">
|
||||
<h3 style="margin-top: 1rem">Add label</h3>
|
||||
|
||||
<form method="POST">
|
||||
{{csrf_token()}}
|
||||
<div class="form-row">
|
||||
<div class="col-auto">
|
||||
<div class="form-group" style="width: 4rem">
|
||||
<label for="color">Color</label>
|
||||
<input
|
||||
type="color"
|
||||
class="form-control {{ valid.cls("color") }}"
|
||||
id="color"
|
||||
name="color"
|
||||
style="height: 2.2rem"
|
||||
value="{{ color or "#ffffff" }}" />
|
||||
{{valid.summary("color")}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control {{ valid.cls("name") }}"
|
||||
maxlength="2048"
|
||||
id="name"
|
||||
name="name"
|
||||
value="{{ name or "" }}" />
|
||||
{{valid.summary("name")}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Add {{icon("caret-right")}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -46,6 +46,12 @@ def ticket_unassign_url(ticket):
|
|||
name=ticket.tracker.name,
|
||||
ticket_id=ticket.scoped_id)
|
||||
|
||||
def label_edit_url(label):
|
||||
return url_for("tracker.label_edit_GET",
|
||||
owner=label.tracker.owner.canonical_name,
|
||||
name=label.tracker.name,
|
||||
label_name=label.name)
|
||||
|
||||
def label_search_url(label, terms=""):
|
||||
"""Return the URL to the tracker page listing all tickets which have the
|
||||
label applied."""
|
||||
|
|
Loading…
Reference in New Issue