Implement editing labels

as requested in https://todo.sr.ht/~sircmpwn/todo.sr.ht/159
This commit is contained in:
Ivan Habunek 2019-10-23 14:50:23 +02:00 committed by Drew DeVault
parent 53cb5b596c
commit 7d018e76e1
5 changed files with 212 additions and 70 deletions

View File

@ -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):

View File

@ -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)

View File

@ -0,0 +1,79 @@
{% extends "layout.html" %}
{% block title %}
<title>
labels
&mdash;
{{tracker.name}}
&mdash;
{{ 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 %}

View File

@ -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 %}

View File

@ -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."""