Flesh out schema and update templates/create
This commit is contained in:
parent
f2885f8777
commit
bb22b8d791
|
@ -1,5 +1,6 @@
|
|||
from flask import render_template, request
|
||||
from flask_login import LoginManager, current_user
|
||||
from jinja2 import Markup
|
||||
import locale
|
||||
import urllib
|
||||
|
||||
|
@ -27,23 +28,40 @@ try:
|
|||
except:
|
||||
pass
|
||||
|
||||
def oauth_url(return_to):
|
||||
return "{}/oauth/authorize?client_id={}&scopes=profile&state={}".format(
|
||||
meta_sr_ht, meta_client_id, urllib.parse.quote_plus(return_to))
|
||||
|
||||
from todosrht.blueprints.html import html
|
||||
from todosrht.blueprints.auth import auth
|
||||
from todosrht.blueprints.tracker import tracker
|
||||
|
||||
app.register_blueprint(html)
|
||||
app.register_blueprint(auth)
|
||||
app.register_blueprint(tracker)
|
||||
|
||||
meta_sr_ht = cfg("network", "meta")
|
||||
meta_client_id = cfg("meta.sr.ht", "oauth-client-id")
|
||||
|
||||
def oauth_url(return_to):
|
||||
return "{}/oauth/authorize?client_id={}&scopes=profile&state={}".format(
|
||||
meta_sr_ht, meta_client_id, urllib.parse.quote_plus(return_to))
|
||||
def tracker_name(tracker):
|
||||
split = tracker.name.split("/")
|
||||
name = split[-1]
|
||||
if len(name) == 0:
|
||||
return name
|
||||
parts = split[:-1]
|
||||
user = "~" + tracker.owner.username
|
||||
return Markup(
|
||||
"/".join([
|
||||
"<a href='/{}/{}'>{}</a>".format(user, "/".join(parts[:i + 1]), p)
|
||||
for i, p in enumerate(parts)
|
||||
]) + "/" + name
|
||||
)
|
||||
|
||||
@app.context_processor
|
||||
def inject():
|
||||
return {
|
||||
"oauth_url": oauth_url(request.full_path),
|
||||
"current_user": User.query.filter(User.id == current_user.id).first() \
|
||||
if current_user else None
|
||||
if current_user else None,
|
||||
"format_tracker_name": tracker_name
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ def oauth_callback():
|
|||
user.oauth_token_scopes = scopes
|
||||
db.session.commit()
|
||||
|
||||
login_user(user)
|
||||
login_user(user, remember=True)
|
||||
if not state or not state.startswith("/"):
|
||||
return redirect("/")
|
||||
else:
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import re
|
||||
import string
|
||||
from flask import Blueprint, render_template, request, url_for, abort, redirect
|
||||
from flask_login import current_user
|
||||
from todosrht.decorators import loginrequired
|
||||
from todosrht.types import Tracker, User
|
||||
from srht.validation import Validation
|
||||
from srht.database import db
|
||||
|
||||
tracker = Blueprint("tracker", __name__)
|
||||
|
||||
name_re = re.compile(r"^([a-z][a-z0-9_.-]*/?)+$")
|
||||
|
||||
@tracker.route("/tracker/create")
|
||||
@loginrequired
|
||||
def create_GET():
|
||||
return render_template("tracker-create.html")
|
||||
|
||||
@tracker.route("/tracker/create", methods=["POST"])
|
||||
@loginrequired
|
||||
def create_POST():
|
||||
valid = Validation(request)
|
||||
name = valid.require("tracker_name", friendly_name="Name")
|
||||
desc = valid.optional("tracker_desc")
|
||||
if not valid.ok:
|
||||
return render_template("tracker-create.html", **valid.kwargs), 400
|
||||
|
||||
valid.expect(2 < len(name) < 256,
|
||||
"Must be between 2 and 256 characters",
|
||||
field="tracker_name")
|
||||
valid.expect(not valid.ok or name[0] in string.ascii_lowercase,
|
||||
"Must begin with a lowercase letter", field="tracker_name")
|
||||
valid.expect(not valid.ok or name_re.match(name),
|
||||
"Only lowercase alphanumeric characters or -./",
|
||||
field="tracker_name")
|
||||
valid.expect(not desc or len(desc) < 4096,
|
||||
"Must be less than 4096 characters",
|
||||
field="tracker_desc")
|
||||
if not valid.ok:
|
||||
return render_template("tracker-create.html", **valid.kwargs), 400
|
||||
|
||||
tracker = (Tracker.query
|
||||
.filter(Tracker.owner_id == current_user.id)
|
||||
.filter(Tracker.name == name)
|
||||
).first()
|
||||
valid.expect(not tracker,
|
||||
"A tracker by this name already exists",
|
||||
field="tracker_name")
|
||||
if not valid.ok:
|
||||
return render_template("tracker-create.html", **valid.kwargs), 400
|
||||
|
||||
tracker = Tracker()
|
||||
tracker.owner_id = current_user.id
|
||||
tracker.name = name
|
||||
tracker.description = desc
|
||||
db.session.add(tracker)
|
||||
db.session.commit()
|
||||
|
||||
if "create-configure" in valid:
|
||||
return redirect(url_for(".tracker_configure_GET",
|
||||
owner=current_user.username,
|
||||
name=name))
|
||||
|
||||
return redirect(url_for(".tracker_GET",
|
||||
owner="~" + current_user.username,
|
||||
name=name))
|
||||
|
||||
@tracker.route("/<owner>/<path:name>")
|
||||
def tracker_GET(owner, name):
|
||||
if owner.startswith("~"):
|
||||
owner = User.query.filter(User.username == owner[1:]).first()
|
||||
if not owner:
|
||||
abort(404)
|
||||
print(name)
|
||||
tracker = (Tracker.query
|
||||
.filter(Tracker.owner_id == owner.id)
|
||||
.filter(Tracker.name == name.lower())
|
||||
).first()
|
||||
if not tracker:
|
||||
abort(404)
|
||||
else:
|
||||
abort(404) # TODO
|
||||
return render_template("tracker.html", tracker=tracker)
|
||||
|
||||
@tracker.route("/<owner>/<path:name>/configure")
|
||||
@loginrequired
|
||||
def tracker_configure_GET(owner, name):
|
||||
pass
|
||||
|
||||
@tracker.route("/<owner>/<path:name>/submit")
|
||||
@loginrequired
|
||||
def tracker_submit_GET(owner, name):
|
||||
pass
|
|
@ -0,0 +1,15 @@
|
|||
from flask import redirect, request, abort
|
||||
from flask_login import current_user
|
||||
from functools import wraps
|
||||
from todosrht.app import oauth_url
|
||||
|
||||
import urllib
|
||||
|
||||
def loginrequired(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not current_user:
|
||||
return redirect(oauth_url(request.url))
|
||||
else:
|
||||
return f(*args, **kwargs)
|
||||
return wrapper
|
|
@ -0,0 +1,51 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2>Create new tracker</h2>
|
||||
<form method="POST" action="/tracker/create">
|
||||
<div class="form-group {{valid.cls("tracker_name")}}">
|
||||
<label for="tracker_name">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
name="tracker_name"
|
||||
id="tracker_name"
|
||||
class="form-control"
|
||||
value="{{ tracker_name or "" }}"
|
||||
aria-describedby="tracker_name-help" />
|
||||
{{valid.summary("tracker_name")}}
|
||||
<p id="tracker_name-help" class="form-text text-muted">
|
||||
Use slashes to nest trackers (i.e. my-project/linux)
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group {{valid.cls("tracker_desc")}}">
|
||||
<label for="tracker_desc">Description</label>
|
||||
<textarea
|
||||
name="tracker_desc"
|
||||
id="tracker_desc"
|
||||
class="form-control"
|
||||
value="{{ tracker_desc or "" }}"
|
||||
rows="5"
|
||||
aria-describedby="tracker_desc-help">{{tracker_desc or ""}}</textarea>
|
||||
<p id="tracker_desc-help" class="form-text text-muted">
|
||||
Markdown supported
|
||||
</p>
|
||||
{{valid.summary("tracker_desc")}}
|
||||
</div>
|
||||
{{valid.summary()}}
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-default"
|
||||
name="create"
|
||||
>Create</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-default"
|
||||
name="create-configure"
|
||||
>Create & configure</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,185 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2>{{ format_tracker_name(tracker) }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{{ tracker.description | md }}
|
||||
<h3 style="margin-top: 1rem">Submit ticket</h3>
|
||||
<form method="POST" action="{{
|
||||
url_for(".tracker_submit_GET",
|
||||
owner=tracker.owner.username,
|
||||
name=tracker.name
|
||||
)
|
||||
}}">
|
||||
<div class="form-group">
|
||||
<label for="title">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="title"
|
||||
name="title" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="description"
|
||||
name="description"
|
||||
placeholder="Markdown supported"
|
||||
rows="5"></textarea>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-default"
|
||||
>Submit</button>
|
||||
<label class="form-check-label" style="margin-left: 0.5rem">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
name="another"
|
||||
style="position: relative; top: 2px;"
|
||||
{% if another %}
|
||||
checked
|
||||
{% endif %}> Submit another?
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<form>
|
||||
<input
|
||||
name="search"
|
||||
type="text"
|
||||
placeholder="Search tickets... status:closed order:updated submitter:me"
|
||||
class="form-control"
|
||||
value="{{ search if search else "" }}" />
|
||||
</form>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link active"
|
||||
href="#">open tickets</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
href="#">closed tickets</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
href="#">board owner</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
href="#">can add</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
href="#">arbitrary tabs</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
href="#">users can also</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
href="#">add personal ones</a>
|
||||
</li>
|
||||
</ul>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Title</th>
|
||||
<th>Updated</th>
|
||||
<th>Submitter</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for n in range(0, 3) %}
|
||||
<tr>
|
||||
<td><a href="#">#1289</a></td>
|
||||
<td>Won't start weston-terminal if WLC_XWAYLAND != 0</td>
|
||||
<td>3 days ago</td>
|
||||
<td><a href="#">johalun</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">#1287</a></td>
|
||||
<td>Variable prefix of another variable</td>
|
||||
<td>5 days ago</td>
|
||||
<td><a href="#">emersion</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">#1286</a></td>
|
||||
<td>Remove titlebar in tabbed mode too</td>
|
||||
<td>6 days ago</td>
|
||||
<td><a href="#">ormung</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">#1284</a></td>
|
||||
<td>Memory leaks in swaybar</td>
|
||||
<td>11 days ago</td>
|
||||
<td><a href="#">4e554c4c</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">#1278</a></td>
|
||||
<td>Chromium (Aura) context menus do not hold window focus.</td>
|
||||
<td>14 days ago</td>
|
||||
<td><a href="#">Zach-Button</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">#1266</a></td>
|
||||
<td>Inconsistent Caps Lock behavior</td>
|
||||
<td>19 days ago</td>
|
||||
<td><a href="#">zasma</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">#1260</a></td>
|
||||
<td>Sway on Void</td>
|
||||
<td>23 days ago</td>
|
||||
<td><a href="#">julio641742</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">#1245</a></td>
|
||||
<td>swaygrab appears to interpret spaces in filename</td>
|
||||
<td>Jun 19</td>
|
||||
<td><a href="#">louisch</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">#1230</a></td>
|
||||
<td>Build failure - gcc: fatal error: cannot specify -o with -c, -S, or -E with multiple files</td>
|
||||
<td>May 30</td>
|
||||
<td><a href="#">ng-0</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">#1229</a></td>
|
||||
<td>sway floating > spawns at the top left corner instead of at the middle of the screen</td>
|
||||
<td>May 25</td>
|
||||
<td><a href="#">narutowindy</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
</div>
|
||||
<div class="col-md-4 text-centered">
|
||||
[ 1 / 23 ]
|
||||
</div>
|
||||
<div class="col-md-4 text-right">
|
||||
<strong><a href="#">[next]</a></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -2,7 +2,5 @@ from .flagtype import FlagType
|
|||
from .user import User
|
||||
from .ticketaccess import TicketAccess
|
||||
from .tracker import Tracker
|
||||
from .ticketfield import TicketFieldType, TicketField
|
||||
from .ticketfieldvalue import TicketFieldValue
|
||||
from .ticket import Ticket
|
||||
from .ticketauditentry import AuditFieldType, PermissionsTarget, TicketAuditEntry
|
||||
from .ticketsubscription import TicketSubscription
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
import sqlalchemy as sa
|
||||
from srht.database import Base
|
||||
from todosrht.types import TicketAccess, FlagType
|
||||
from todosrht.types import FlagType, TicketAccess
|
||||
from enum import Enum
|
||||
|
||||
class Ticket(Base):
|
||||
"""
|
||||
Represents a ticket filed in the system. The default permissions are
|
||||
inherited from the tracker configuration, but may be edited to i.e.
|
||||
|
||||
- Give an arbitrary edit/view/whatever access
|
||||
- Remove a specific user's permission to edit
|
||||
- Allow the public to comment on an otherwise uncommentable issue
|
||||
- Lock an issue from further discussion from non-contributors
|
||||
- etc
|
||||
"""
|
||||
__tablename__ = 'ticket'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
ticket_id = sa.Column(sa.Integer, index=True)
|
||||
"""The ID specific to this tracker, appears in URLs etc"""
|
||||
name = sa.Column(sa.Unicode(2048), nullable=False)
|
||||
|
||||
tracker_id = sa.Column(sa.Integer, sa.ForeignKey("tracker.id"), nullable=False)
|
||||
tracker = sa.orm.relationship("Tracker", backref=sa.orm.backref("tickets"))
|
||||
|
||||
submitter_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
|
||||
submitter = sa.orm.relationship("User", backref=sa.orm.backref("tickets"))
|
||||
submitter = sa.orm.relationship("User", backref=sa.orm.backref("submitted"))
|
||||
|
||||
default_user_perms = sa.Column(FlagType(TicketAccess), nullable=False)
|
||||
title = sa.Column(sa.Unicode(2048), nullable=False)
|
||||
description = sa.Column(sa.Unicode(16384), nullable=False)
|
||||
user_agent = sa.Column(sa.Unicode(2048))
|
||||
|
||||
user_perms = sa.Column(FlagType(TicketAccess),
|
||||
nullable=False,
|
||||
default=TicketAccess.browse + TicketAccess.submit + TicketAccess.comment)
|
||||
"""Permissions given to any logged in user"""
|
||||
|
||||
default_submitter_perms = sa.Column(FlagType(TicketAccess), nullable=False)
|
||||
"""Permissions granted to the ticket submitter"""
|
||||
submitter_perms = sa.Column(FlagType(TicketAccess),
|
||||
nullable=False,
|
||||
default=TicketAccess.browse + TicketAccess.edit + TicketAccess.comment)
|
||||
"""Permissions granted to submitters for their own tickets"""
|
||||
|
||||
default_committer_perms = sa.Column(FlagType(TicketAccess), nullable=False)
|
||||
committer_perms = sa.Column(FlagType(TicketAccess),
|
||||
nullable=False,
|
||||
default=TicketAccess.browse + TicketAccess.submit + TicketAccess.comment)
|
||||
"""Permissions granted to people who have authored commits in the linked git repo"""
|
||||
|
||||
default_anonymous_perms = sa.Column(FlagType(TicketAccess), nullable=False)
|
||||
anonymous_perms = sa.Column(FlagType(TicketAccess),
|
||||
nullable=False,
|
||||
default=TicketAccess.browse)
|
||||
"""Permissions granted to anonymous (non-logged in) users"""
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import sqlalchemy as sa
|
||||
from srht.database import Base
|
||||
from enum import Enum
|
||||
|
||||
class TicketAssignee(Base):
|
||||
__tablename__ = 'ticket_assignee'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
|
||||
ticket_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"), nullable=False)
|
||||
ticket = sa.orm.relationship("Ticket", backref=sa.orm.backref("assignees"))
|
||||
|
||||
assignee_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
|
||||
assignee = sa.orm.relationship("User",
|
||||
backref=sa.orm.backref("assigned"),
|
||||
foreign_keys="assignee_id")
|
||||
|
||||
assigner_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
|
||||
assigner = sa.orm.relationship("User",
|
||||
backref=sa.orm.backref("assigned"),
|
||||
foreign_keys="assignee_id")
|
||||
|
||||
role = sa.Column(sa.Unicode(256))
|
|
@ -1,95 +0,0 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils as sau
|
||||
from todosrht.types import FlagType, TicketAccess
|
||||
from srht.database import Base
|
||||
from enum import Enum
|
||||
|
||||
class AuditFieldType(Enum):
|
||||
"""Describes what kind of field was updated in an audit log event"""
|
||||
name = "name"
|
||||
permissions = "permissions"
|
||||
tracker = "tracker"
|
||||
custom_field = "custom_field"
|
||||
custom_event = "custom_event"
|
||||
|
||||
class PermissionsTarget(Enum):
|
||||
"""Describes the target of an update to ticket permissions"""
|
||||
anonymous = "anonymous"
|
||||
logged_in = "logged_in"
|
||||
submitted = "submitter"
|
||||
committer = "committer"
|
||||
user = "user"
|
||||
"""A specific named user"""
|
||||
|
||||
class TicketAuditEntry(Base):
|
||||
"""
|
||||
Records an event that has occured to a ticket. The field_type tells you
|
||||
what kind of field was affected, which is used to disambiguate the affected
|
||||
columns in the database.
|
||||
|
||||
AuditFieldType.name is used when the ticket is renamed. old_name and
|
||||
new_name are valid for these events.
|
||||
|
||||
AuditFieldType.permissions is used permissions are changed. old_permissions
|
||||
and new_permissions are valid for these events, as well as
|
||||
permissions_target, which describes what kind of user was affected by the
|
||||
change. If permissions_target == PermissionsTarget.user, a specific user's
|
||||
permissions were edited and permissions_user is valid.
|
||||
|
||||
AuditFieldType.tracker is used when a ticket is moved between trackers.
|
||||
old_tracker and new_tracker are valid for this event.
|
||||
|
||||
AuditFieldType.custom_field is when a custom field is edited.
|
||||
old_custom_value and new_custom_value are valid for this event.
|
||||
|
||||
AuditFieldType.custom_event is used for events submitted through the API
|
||||
(i.e. build status updates). oauth_client and custom_text are valid for
|
||||
this event.
|
||||
"""
|
||||
__tablename__ = 'ticket_audit_entry'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
ticket_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"), nullable=False)
|
||||
ticket = sa.orm.relationship("Ticket",
|
||||
backref=sa.orm.backref("audit_log"))
|
||||
field_type = sa.Column(sau.ChoiceType(AuditFieldType), nullable=False)
|
||||
user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
|
||||
user = sa.orm.relationship("User",
|
||||
foreign_keys=[user_id])
|
||||
"""The user who executed the change"""
|
||||
ticket_field_id = sa.Column(sa.Integer, sa.ForeignKey("ticket_field.id"))
|
||||
ticket_field = sa.orm.relationship("TicketField")
|
||||
#oauth_client_id = sa.Column(sa.Integer, sa.ForeignKey("oauth_client.id"))
|
||||
#oauth_client = sa.orm.relationship("OAuthClient")
|
||||
|
||||
custom_text = sa.Column(sa.Unicode(4096))
|
||||
"""Markdown, typically used for custom events submitted via API"""
|
||||
|
||||
old_name = sa.Column(sa.Unicode(2048))
|
||||
new_name = sa.Column(sa.Unicode(2048))
|
||||
|
||||
old_permissions = sa.Column(FlagType(TicketAccess))
|
||||
new_permissions = sa.Column(FlagType(TicketAccess))
|
||||
permissions_target = sa.Column(sau.ChoiceType(PermissionsTarget))
|
||||
permissions_user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"))
|
||||
permissions_user = sa.orm.relationship("User",
|
||||
foreign_keys=[permissions_user_id])
|
||||
|
||||
old_tracker_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("tracker.id"))
|
||||
old_tracker = sa.orm.relationship("Tracker",
|
||||
foreign_keys=[old_tracker_id])
|
||||
new_tracker_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("tracker.id"))
|
||||
new_tracker = sa.orm.relationship("Tracker",
|
||||
foreign_keys=[new_tracker_id])
|
||||
|
||||
old_custom_value_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("ticket_field_value.id"))
|
||||
old_custom_value = sa.orm.relationship("TicketFieldValue",
|
||||
foreign_keys=[old_custom_value_id])
|
||||
|
||||
new_custom_value_id = sa.Column(sa.Integer,
|
||||
sa.ForeignKey("ticket_field_value.id"))
|
||||
new_custom_value = sa.orm.relationship("TicketFieldValue",
|
||||
foreign_keys=[new_custom_value_id])
|
|
@ -1,19 +0,0 @@
|
|||
import sqlalchemy as sa
|
||||
from srht.database import Base
|
||||
|
||||
class TicketComment(Base):
|
||||
__tablename__ = "ticket_comment"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
|
||||
ticket_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"), nullable=False)
|
||||
ticket = sa.orm.relationship("Ticket", backref=sa.orm.backref("fields"))
|
||||
|
||||
user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
|
||||
user = sa.orm.relationship("User", backref=sa.orm.backref("comments"))
|
||||
|
||||
text = sa.Column(sa.Unicode(16384), nullable=False)
|
||||
"""Markdown"""
|
||||
visible = sa.Column(sa.Boolean, nullable=False, default=True)
|
||||
"""Deleted comments stay in the system, but are removed from the listing"""
|
|
@ -0,0 +1,22 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils as sau
|
||||
from srht.database import Base
|
||||
from todosrht.types import FlagType
|
||||
from enum import Enum
|
||||
|
||||
class TicketSubscription(Base):
|
||||
"""One of user, email, or webhook will be valid. The rest will be null."""
|
||||
__tablename__ = 'ticket_subscription'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
|
||||
ticket_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"), nullable=False)
|
||||
ticket = sa.orm.relationship("Ticket", backref=sa.orm.backref("subscriptions"))
|
||||
|
||||
user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"))
|
||||
user = sa.orm.relationship("User", backref=sa.orm.backref("subscriptions"))
|
||||
|
||||
email = sa.Column(sa.Unicode(512))
|
||||
|
||||
webhook = sa.Column(sa.Unicode(1024))
|
Loading…
Reference in New Issue