Implement tracker deletion

This commit is contained in:
Drew DeVault 2018-12-12 22:48:43 -05:00
parent b0d6e23ce2
commit 8381642d66
13 changed files with 491 additions and 56 deletions

View File

@ -0,0 +1,314 @@
"""Add CASCADE to tracker deletions
Revision ID: cd94e721e6b0
Revises: 030c8f83a75d
Create Date: 2018-12-12 22:16:01.893712
"""
# revision identifiers, used by Alembic.
revision = 'cd94e721e6b0'
down_revision = '030c8f83a75d'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.drop_constraint(
constraint_name="event_ticket_id_fkey",
table_name="event",
type_="foreignkey")
op.create_foreign_key(
constraint_name="event_ticket_id_fkey",
source_table="event",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="event_comment_id_fkey",
table_name="event",
type_="foreignkey")
op.create_foreign_key(
constraint_name="event_comment_id_fkey",
source_table="event",
referent_table="ticket_comment",
local_cols=["comment_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="event_label_id_fkey",
table_name="event",
type_="foreignkey")
op.create_foreign_key(
constraint_name="event_label_id_fkey",
source_table="event",
referent_table="label",
local_cols=["label_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="event_notification_event_id_fkey",
table_name="event_notification",
type_="foreignkey")
op.create_foreign_key(
constraint_name="event_notification_event_id_fkey",
source_table="event_notification",
referent_table="event",
local_cols=["event_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="label_tracker_id_fkey",
table_name="label",
type_="foreignkey")
op.create_foreign_key(
constraint_name="label_tracker_id_fkey",
source_table="label",
referent_table="tracker",
local_cols=["tracker_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="ticket_label_ticket_id_fkey",
table_name="ticket_label",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_label_ticket_id_fkey",
source_table="ticket_label",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="ticket_label_label_id_fkey",
table_name="ticket_label",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_label_label_id_fkey",
source_table="ticket_label",
referent_table="label",
local_cols=["label_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="ticket_tracker_id_fkey",
table_name="ticket",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_tracker_id_fkey",
source_table="ticket",
referent_table="tracker",
local_cols=["tracker_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="ticket_dupe_of_id_fkey",
table_name="ticket",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_dupe_of_id_fkey",
source_table="ticket",
referent_table="ticket",
local_cols=["dupe_of_id"],
remote_cols=["id"],
ondelete="SET NULL")
op.drop_constraint(
constraint_name="ticket_comment_ticket_id_fkey",
table_name="ticket_comment",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_comment_ticket_id_fkey",
source_table="ticket_comment",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="ticket_subscription_tracker_id_fkey",
table_name="ticket_subscription",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_subscription_tracker_id_fkey",
source_table="ticket_subscription",
referent_table="tracker",
local_cols=["tracker_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="ticket_subscription_ticket_id_fkey",
table_name="ticket_subscription",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_subscription_ticket_id_fkey",
source_table="ticket_subscription",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="ticket_seen_ticket_id_fkey",
table_name="ticket_seen",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_seen_ticket_id_fkey",
source_table="ticket_seen",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"],
ondelete="CASCADE")
op.drop_constraint(
constraint_name="ticket_assignee_ticket_id_fkey",
table_name="ticket_assignee",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_assignee_ticket_id_fkey",
source_table="ticket_assignee",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"],
ondelete="CASCADE")
def downgrade():
op.drop_constraint(
constraint_name="event_ticket_id_fkey",
table_name="event",
type_="foreignkey")
op.create_foreign_key(
constraint_name="event_ticket_id_fkey",
source_table="event",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="event_comment_id_fkey",
table_name="event",
type_="foreignkey")
op.create_foreign_key(
constraint_name="event_comment_id_fkey",
source_table="event",
referent_table="ticket_comment",
local_cols=["comment_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="event_label_id_fkey",
table_name="event",
type_="foreignkey")
op.create_foreign_key(
constraint_name="event_label_id_fkey",
source_table="event",
referent_table="label",
local_cols=["label_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="event_notification_event_id_fkey",
table_name="event_notification",
type_="foreignkey")
op.create_foreign_key(
constraint_name="event_notification_event_id_fkey",
source_table="event_notification",
referent_table="event",
local_cols=["event_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="label_tracker_id_fkey",
table_name="label",
type_="foreignkey")
op.create_foreign_key(
constraint_name="label_tracker_id_fkey",
source_table="label",
referent_table="tracker",
local_cols=["tracker_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="ticket_label_ticket_id_fkey",
table_name="ticket_label",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_label_ticket_id_fkey",
source_table="ticket_label",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="ticket_label_label_id_fkey",
table_name="ticket_label",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_label_label_id_fkey",
source_table="ticket_label",
referent_table="label",
local_cols=["label_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="ticket_tracker_id_fkey",
table_name="ticket",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_tracker_id_fkey",
source_table="ticket",
referent_table="tracker",
local_cols=["tracker_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="ticket_dupe_of_id_fkey",
table_name="ticket",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_dupe_of_id_fkey",
source_table="ticket",
referent_table="ticket",
local_cols=["dupe_of_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="ticket_comment_ticket_id_fkey",
table_name="ticket_comment",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_comment_ticket_id_fkey",
source_table="ticket_comment",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="ticket_subscription_tracker_id_fkey",
table_name="ticket_subscription",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_subscription_tracker_id_fkey",
source_table="ticket_subscription",
referent_table="tracker",
local_cols=["tracker_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="ticket_subscription_ticket_id_fkey",
table_name="ticket_subscription",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_subscription_ticket_id_fkey",
source_table="ticket_subscription",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="ticket_seen_ticket_id_fkey",
table_name="ticket_seen",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_seen_ticket_id_fkey",
source_table="ticket_seen",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"])
op.drop_constraint(
constraint_name="ticket_assignee_ticket_id_fkey",
table_name="ticket_assignee",
type_="foreignkey")
op.create_foreign_key(
constraint_name="ticket_assignee_ticket_id_fkey",
source_table="ticket_assignee",
referent_table="ticket",
local_cols=["ticket_id"],
remote_cols=["id"])

View File

@ -1,4 +1,4 @@
from flask import Blueprint, render_template, request, abort
from flask import Blueprint, render_template, request, abort, session
from flask_login import current_user
from todosrht.access import get_tracker, get_access
from todosrht.types import Tracker, Ticket, Event, EventNotification, EventType
@ -68,12 +68,15 @@ def index():
.order_by(Event.created.desc()))
events = events.limit(10).all()
notice = session.get("notice")
if notice:
del session["notice"]
return render_template("dashboard.html",
trackers=trackers,
trackers=trackers, notice=notice,
tracker_list_msg="Your Trackers",
more_trackers=total_trackers > limit_trackers,
events=events,
EventType=EventType)
events=events, EventType=EventType)
@html.route("/~<username>")
def user_GET(username):

View File

@ -11,7 +11,7 @@ from todosrht.types import TicketSubscription
from todosrht.types import Event, EventType, EventNotification
from todosrht.types import Tracker, Ticket, TicketStatus, TicketAccess
from todosrht.types import Label, TicketLabel, TicketSeen, TicketComment
from todosrht.urls import tracker_url
from todosrht.urls import tracker_url, user_url
from srht.config import cfg
from srht.database import db
from srht.flask import paginate_query, loginrequired
@ -204,9 +204,7 @@ def settings_details_GET(owner, name):
if current_user.id != tracker.owner_id:
abort(403)
return render_template("tracker-details.html",
view="details", tracker=tracker,
access_type_list=TicketAccess,
access_help_map=access_help_map)
view="details", tracker=tracker)
@loginrequired
@tracker.route("/<owner>/<name>/settings/details", methods=["POST"])
@ -224,8 +222,7 @@ def settings_details_POST(owner, name):
field="tracker_desc")
if not valid.ok:
return render_template("tracker-details.html",
tracker=tracker, access_type_list=TicketAccess,
access_help_map=access_help_map, **valid.kwargs), 400
tracker=tracker, **valid.kwargs), 400
tracker.description = desc
db.session.commit()
@ -261,7 +258,7 @@ def settings_access_POST(owner, name):
#perm_commit = parse_html_perms('commit', valid)
if not valid.ok:
return render_template("tracker-details.html",
return render_template("tracker-access.html",
tracker=tracker, access_type_list=TicketAccess,
access_help_map=access_help_map, **valid.kwargs), 400
@ -273,8 +270,37 @@ def settings_access_POST(owner, name):
db.session.commit()
return redirect(tracker_url(tracker))
@tracker.route("/<owner>/<name>/submit", methods=["POST"])
@loginrequired
@tracker.route("/<owner>/<name>/settings/delete")
def settings_delete_GET(owner, name):
tracker, access = get_tracker(owner, name)
if not tracker:
abort(404)
if current_user.id != tracker.owner_id:
abort(403)
return render_template("tracker-delete.html",
view="delete", tracker=tracker)
@loginrequired
@tracker.route("/<owner>/<name>/settings/delete", methods=["POST"])
def settings_delete_POST(owner, name):
tracker, access = get_tracker(owner, name)
if not tracker:
abort(404)
if current_user.id != tracker.owner_id:
abort(403)
session["notice"] = f"{tracker.owner}/{tracker.name} was deleted."
# SQLAlchemy shits itself on some of our weird constraints/relationships
# so fuck it, posgres knows what to do here
tracker_id = tracker.id
assert isinstance(tracker_id, int)
db.session.expunge_all()
db.engine.execute(f"DELETE FROM tracker WHERE id = {tracker_id};")
db.session.commit()
return redirect(url_for("html.index"))
@loginrequired
@tracker.route("/<owner>/<name>/submit", methods=["POST"])
def tracker_submit_POST(owner, name):
tracker, access = get_tracker(owner, name, True)
if not tracker:

View File

@ -15,6 +15,11 @@
<a href="https://man.sr.ht/todo.sr.ht">available here</a>.
</p>
{% endif %}
{% if notice %}
<div class="alert alert-success">
{{ notice }}
</div>
{% endif %}
{% if profile %}
{% if profile.get("location") %}

View File

@ -29,7 +29,9 @@
name=tracker.name), "access")}}
</li>
<li class="nav-item">
{{link("#", "delete")}}
{{link(url_for(".settings_delete_GET",
owner=tracker.owner.canonical_name(),
name=tracker.name), "delete")}}
</li>
</ul>
</div>

View File

@ -0,0 +1,25 @@
{% extends "settings.html" %}
{% block title %}
<title>Delete {{tracker.owner}}/{{tracker.name}} &mdash; {{ cfg("sr.ht", "site-name") }}</title>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<p>
This will permanently delete your tracker,
<strong>{{ tracker.owner }}/{{ tracker.name }}</strong>, and all of
its tickets. This cannot be undone.
</p>
<form method="POST">
{{csrf_token()}}
<button type="submit" class="btn btn-danger">
Proceed and delete {{icon("caret-right")}}
</button>
<a
href="/{{ tracker.owner }}/{{ tracker.name }}"
class="btn btn-default"
>Nevermind</a>
</form>
</div>
</div>
{% endblock %}

View File

@ -27,17 +27,26 @@ class Event(Base):
new_status = sa.Column(FlagType(TicketStatus), default=0)
new_resolution = sa.Column(FlagType(TicketResolution), default=0)
user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
user = sa.orm.relationship("User", backref=sa.orm.backref("events"))
user_id = sa.Column(sa.Integer,
sa.ForeignKey("user.id"),
nullable=False)
user = sa.orm.relationship("User",
backref=sa.orm.backref("events"))
ticket_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"), nullable=False)
ticket = sa.orm.relationship("Ticket", backref=sa.orm.backref("events"))
ticket_id = sa.Column(sa.Integer,
sa.ForeignKey("ticket.id", ondelete="CASCADE"),
nullable=False)
ticket = sa.orm.relationship("Ticket",
backref=sa.orm.backref("events", cascade="all, delete-orphan"))
comment_id = sa.Column(sa.Integer, sa.ForeignKey("ticket_comment.id"))
comment = sa.orm.relationship("TicketComment")
comment_id = sa.Column(sa.Integer,
sa.ForeignKey("ticket_comment.id", ondelete="CASCADE"))
comment = sa.orm.relationship("TicketComment", cascade="all, delete")
label_id = sa.Column(sa.Integer, sa.ForeignKey('label.id'))
label = sa.orm.relationship("Label", backref=sa.orm.backref("events"))
label_id = sa.Column(sa.Integer,
sa.ForeignKey('label.id', ondelete="CASCADE"))
label = sa.orm.relationship("Label",
backref=sa.orm.backref("events", cascade="all, delete-orphan"))
def __repr__(self):
return '<Event {}>'.format(self.id)
@ -47,11 +56,19 @@ class EventNotification(Base):
id = sa.Column(sa.Integer, primary_key=True)
created = sa.Column(sa.DateTime, nullable=False)
event_id = sa.Column(sa.Integer, sa.ForeignKey("event.id"), nullable=False)
event = sa.orm.relationship("Event", backref=sa.orm.backref("notifications"))
event_id = sa.Column(sa.Integer,
sa.ForeignKey("event.id", ondelete="CASCADE"),
nullable=False)
event = sa.orm.relationship("Event",
backref=sa.orm.backref("notifications",
cascade="all, delete-orphan"))
user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
user = sa.orm.relationship("User", backref=sa.orm.backref("notifications"))
user_id = sa.Column(sa.Integer,
sa.ForeignKey("user.id"),
nullable=False)
user = sa.orm.relationship("User",
backref=sa.orm.backref("notifications",
cascade="all, delete-orphan"))
def __repr__(self):
return '<EventNotification {} {}>'.format(self.id, self.user.username)

View File

@ -9,8 +9,10 @@ class Label(Base):
updated = sa.Column(sa.DateTime, nullable=False)
tracker_id = sa.Column(sa.Integer,
sa.ForeignKey("tracker.id"), nullable=False)
tracker = sa.orm.relationship("Tracker", backref=sa.orm.backref("labels"))
sa.ForeignKey("tracker.id", ondelete="CASCADE"),
nullable=False)
tracker = sa.orm.relationship("Tracker",
backref=sa.orm.backref("labels", cascade="all, delete-orphan"))
name = sa.Column(sa.Text, nullable=False)
color = sa.Column(sa.Text, nullable=False)
@ -29,17 +31,25 @@ class Label(Base):
class TicketLabel(Base):
__tablename__ = 'ticket_label'
ticket_id = sa.Column(sa.Integer,
sa.ForeignKey('ticket.id'), primary_key=True)
sa.ForeignKey('ticket.id', ondelete="CASCADE"),
primary_key=True)
ticket = sa.orm.relationship("Ticket",
backref=sa.orm.backref("ticket_labels"))
backref=sa.orm.backref("ticket_labels",
cascade="all, delete-orphan"))
label_id = sa.Column(sa.Integer,
sa.ForeignKey('label.id'), primary_key=True)
sa.ForeignKey('label.id', ondelete="CASCADE"),
primary_key=True)
label = sa.orm.relationship("Label",
backref=sa.orm.backref("ticket_labels"))
backref=sa.orm.backref("ticket_labels",
cascade="all, delete-orphan"))
user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
user = sa.orm.relationship("User", backref=sa.orm.backref("ticket_labels"))
user_id = sa.Column(sa.Integer,
sa.ForeignKey("user.id"),
nullable=False)
user = sa.orm.relationship("User",
backref=sa.orm.backref("ticket_labels",
cascade="all, delete-orphan"))
created = sa.Column(sa.DateTime, nullable=False)

View File

@ -9,21 +9,27 @@ class Ticket(Base):
created = sa.Column(sa.DateTime, nullable=False)
updated = sa.Column(sa.DateTime, nullable=False)
tracker_id = sa.Column(sa.Integer, sa.ForeignKey("tracker.id"), nullable=False)
tracker = sa.orm.relationship("Tracker", backref=sa.orm.backref("tickets"))
tracker_id = sa.Column(sa.Integer,
sa.ForeignKey("tracker.id", ondelete="CASCADE"),
nullable=False)
tracker = sa.orm.relationship("Tracker",
backref=sa.orm.backref("tickets", cascade="all, delete-orphan"))
scoped_id = sa.Column(sa.Integer,
nullable=False,
index=True,
unique=sa.UniqueConstraint('scoped_id', 'tracker_id'))
dupe_of_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"))
dupe_of_id = sa.Column(sa.Integer,
sa.ForeignKey("ticket.id", ondelete="SET NULL"))
dupe_of = sa.orm.relationship("Ticket",
backref=sa.orm.backref("dupes"),
remote_side=[id])
submitter_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
submitter = sa.orm.relationship("User", backref=sa.orm.backref("submitted"))
submitter_id = sa.Column(sa.Integer,
sa.ForeignKey("user.id"), nullable=False)
submitter = sa.orm.relationship("User",
backref=sa.orm.backref("submitted", cascade="all, delete-orphan"))
title = sa.Column(sa.Unicode(2048), nullable=False)
description = sa.Column(sa.Unicode(16384))

View File

@ -7,13 +7,20 @@ class TicketAssignee(Base):
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"))
ticket_id = sa.Column(sa.Integer,
sa.ForeignKey("ticket.id", ondelete="CASCADE"),
nullable=False)
ticket = sa.orm.relationship("Ticket",
backref=sa.orm.backref("assignees", cascade="all, delete-orphan"))
assignee_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
assignee_id = sa.Column(sa.Integer,
sa.ForeignKey("user.id"),
nullable=False)
assignee = sa.orm.relationship("User", foreign_keys=[assignee_id])
assigner_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
assigner_id = sa.Column(sa.Integer,
sa.ForeignKey("user.id"),
nullable=False)
assigner = sa.orm.relationship("User", foreign_keys=[assigner_id])
role = sa.Column(sa.Unicode(256))

View File

@ -9,10 +9,14 @@ class TicketComment(Base):
created = sa.Column(sa.DateTime, nullable=False)
updated = sa.Column(sa.DateTime, nullable=False)
submitter_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"), nullable=False)
submitter_id = sa.Column(sa.Integer,
sa.ForeignKey("user.id"), nullable=False)
submitter = sa.orm.relationship("User")
ticket_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"), nullable=False)
ticket = sa.orm.relationship("Ticket", backref=sa.orm.backref("comments"))
ticket_id = sa.Column(sa.Integer,
sa.ForeignKey("ticket.id", ondelete="CASCADE"),
nullable=False)
ticket = sa.orm.relationship("Ticket",
backref=sa.orm.backref("comments", cascade="all, delete-orphan"))
text = sa.Column(sa.Unicode(16384))

View File

@ -6,12 +6,20 @@ from datetime import datetime
class TicketSeen(Base):
"""Stores the last time a user viewed this ticket. Calculates if comments have been seen."""
__tablename__ = 'ticket_seen'
user_id = sa.Column(sa.Integer, sa.ForeignKey('user.id'), primary_key=True)
ticket_id = sa.Column(sa.Integer, sa.ForeignKey('ticket.id'), primary_key=True)
last_view = sa.Column(sa.DateTime, nullable=False, server_default=sa.sql.func.now())
user_id = sa.Column(sa.Integer,
sa.ForeignKey('user.id'),
primary_key=True)
user = sa.orm.relationship("User")
ticket = sa.orm.relationship("Ticket")
ticket_id = sa.Column(sa.Integer,
sa.ForeignKey('ticket.id', ondelete="CASCADE"),
primary_key=True)
ticket = sa.orm.relationship("Ticket", lazy="joined")
last_view = sa.Column(sa.DateTime,
nullable=False,
server_default=sa.sql.func.now())
def update(self):
self.last_view = datetime.utcnow()

View File

@ -11,16 +11,24 @@ class TicketSubscription(Base):
created = sa.Column(sa.DateTime, nullable=False)
updated = sa.Column(sa.DateTime, nullable=False)
tracker_id = sa.Column(sa.Integer, sa.ForeignKey("tracker.id"))
tracker = sa.orm.relationship("Tracker", backref=sa.orm.backref("subscriptions"))
tracker_id = sa.Column(sa.Integer,
sa.ForeignKey("tracker.id", ondelete="CASCADE"))
tracker = sa.orm.relationship("Tracker",
backref=sa.orm.backref("subscriptions",
cascade="all, delete-orphan"))
"""Used for subscriptions to all tickets on a tracker"""
ticket_id = sa.Column(sa.Integer, sa.ForeignKey("ticket.id"))
ticket = sa.orm.relationship("Ticket", backref=sa.orm.backref("subscriptions"))
ticket_id = sa.Column(sa.Integer,
sa.ForeignKey("ticket.id", ondelete="CASCADE"))
ticket = sa.orm.relationship("Ticket",
backref=sa.orm.backref("subscriptions",
cascade="all, delete-orphan"))
"""Used for subscriptions to specific tickets"""
user_id = sa.Column(sa.Integer, sa.ForeignKey("user.id"))
user = sa.orm.relationship("User", 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))