Use tracker-specific ticket IDs
This commit is contained in:
parent
a44e5a68a6
commit
abc404e79c
|
@ -0,0 +1,62 @@
|
|||
"""Add tracker-scoped ticket IDs
|
||||
|
||||
Revision ID: 237974dd94c4
|
||||
Revises: None
|
||||
Create Date: 2017-09-14 07:01:22.888804
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '237974dd94c4'
|
||||
down_revision = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker, Session as BaseSession, relationship
|
||||
|
||||
Session = sessionmaker()
|
||||
Base = declarative_base()
|
||||
|
||||
class Ticket(Base):
|
||||
__tablename__ = 'ticket'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
scoped_id = sa.Column(sa.Integer)
|
||||
tracker_id = sa.Column(sa.Integer, sa.ForeignKey("tracker.id"), nullable=False)
|
||||
tracker = sa.orm.relationship("Tracker", backref=sa.orm.backref("tickets"))
|
||||
|
||||
class Tracker(Base):
|
||||
__tablename__ = 'tracker'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
next_ticket_id = sa.Column(sa.Integer, nullable=False, default=1)
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind)
|
||||
op.add_column('ticket', sa.Column('scoped_id', sa.Integer()))
|
||||
op.add_column('tracker', sa.Column('next_ticket_id',
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
server_default='1'))
|
||||
session.commit()
|
||||
|
||||
for ticket in session.query(Ticket).all():
|
||||
ticket.scoped_id = ticket.id
|
||||
session.commit()
|
||||
|
||||
for tracker in session.query(Tracker).all():
|
||||
tracker.next_ticket_id = max(ticket.id for ticket in tracker.tickets) + 1
|
||||
session.commit()
|
||||
|
||||
op.create_index(op.f('ix_ticket_scoped_id'), 'ticket', ['scoped_id'])
|
||||
op.create_unique_constraint('uq_ticket_scoped_id_tracker_id', 'ticket',
|
||||
['scoped_id', 'tracker_id'])
|
||||
op.alter_column('ticket', 'scoped_id', nullable=False)
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('tracker', 'next_ticket_id')
|
||||
op.drop_index(op.f('ix_ticket_scoped_id'), table_name='ticket')
|
||||
op.drop_column('ticket', 'scoped_id')
|
||||
# ### end Alembic commands ###
|
|
@ -155,10 +155,13 @@ def tracker_submit_POST(owner, name):
|
|||
ticket = Ticket()
|
||||
ticket.submitter_id = current_user.id
|
||||
ticket.tracker_id = tracker.id
|
||||
ticket.scoped_id = tracker.next_ticket_id
|
||||
tracker.next_ticket_id += 1
|
||||
ticket.user_agent = request.headers.get("User-Agent")
|
||||
ticket.title = title
|
||||
ticket.description = desc
|
||||
db.session.add(ticket)
|
||||
# TODO: Handle unique constraint failure (contention) and retry?
|
||||
db.session.commit()
|
||||
|
||||
if another:
|
||||
|
@ -170,7 +173,7 @@ def tracker_submit_POST(owner, name):
|
|||
return redirect(url_for(".ticket_GET",
|
||||
owner="~" + tracker.owner.username,
|
||||
name=name,
|
||||
ticket_id=ticket.id))
|
||||
ticket_id=ticket.scoped_id))
|
||||
|
||||
def get_access(tracker, ticket):
|
||||
# TODO: flesh out
|
||||
|
@ -182,17 +185,26 @@ def get_access(tracker, ticket):
|
|||
return ticket.user_perms or tracker.default_user_perms
|
||||
return ticket.anonymous_perms or tracker.default_anonymous_perms
|
||||
|
||||
def get_ticket(tracker, ticket_id):
|
||||
ticket = (Ticket.query
|
||||
.filter(Ticket.scoped_id == ticket_id)
|
||||
.filter(Ticket.tracker_id == tracker.id)
|
||||
).first()
|
||||
if not ticket:
|
||||
return None, None
|
||||
access = get_access(tracker, ticket)
|
||||
if not TicketAccess.browse in access:
|
||||
return None, None
|
||||
return ticket, access
|
||||
|
||||
@tracker.route("/<owner>/<path:name>/<int:ticket_id>")
|
||||
def ticket_GET(owner, name, ticket_id):
|
||||
tracker = get_tracker(owner, name)
|
||||
if not tracker:
|
||||
abort(404)
|
||||
ticket = Ticket.query.get(ticket_id)
|
||||
ticket, access = get_ticket(tracker, ticket_id)
|
||||
if not ticket:
|
||||
abort(404)
|
||||
access = get_access(tracker, ticket)
|
||||
if not TicketAccess.browse in access:
|
||||
abort(404)
|
||||
return render_template("ticket.html",
|
||||
tracker=tracker,
|
||||
ticket=ticket,
|
||||
|
@ -204,12 +216,9 @@ def ticket_comment_POST(owner, name, ticket_id):
|
|||
tracker = get_tracker(owner, name)
|
||||
if not tracker:
|
||||
abort(404)
|
||||
ticket = Ticket.query.get(ticket_id)
|
||||
ticket, access = get_ticket(tracker, ticket_id)
|
||||
if not ticket:
|
||||
abort(404)
|
||||
access = get_access(tracker, ticket)
|
||||
if not TicketAccess.browse in access:
|
||||
abort(404)
|
||||
|
||||
valid = Validation(request)
|
||||
text = valid.optional("comment")
|
||||
|
@ -264,9 +273,9 @@ def ticket_comment_POST(owner, name, ticket_id):
|
|||
return redirect(url_for(".ticket_GET",
|
||||
owner="~" + tracker.owner.username,
|
||||
name=tracker.name,
|
||||
ticket_id=ticket.id) + "#comment-" + str(comment.id))
|
||||
ticket_id=ticket.scoped_id) + "#comment-" + str(comment.id))
|
||||
else:
|
||||
return redirect(url_for(".ticket_GET",
|
||||
owner="~" + tracker.owner.username,
|
||||
name=tracker.name,
|
||||
ticket_id=ticket.id))
|
||||
ticket_id=ticket.scoped_id))
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2>
|
||||
{{ format_tracker_name(tracker, full=True) }}/#{{ticket.id}}:
|
||||
{{ format_tracker_name(tracker, full=True) }}/#{{ticket.scoped_id}}:
|
||||
{{ticket.title}}
|
||||
</h2>
|
||||
</div>
|
||||
|
@ -61,7 +61,7 @@
|
|||
url_for(".ticket_comment_POST",
|
||||
owner="~" + tracker.owner.username,
|
||||
name=tracker.name,
|
||||
ticket_id=ticket.id
|
||||
ticket_id=ticket.scoped_id
|
||||
)
|
||||
}}">
|
||||
<div class="form-group {{ valid.cls("comment") }}">
|
||||
|
|
|
@ -92,13 +92,13 @@
|
|||
<td><a href="{{url_for(".ticket_GET",
|
||||
owner="~" + tracker.owner.username,
|
||||
name=tracker.name,
|
||||
ticket_id=ticket.id)}}">#{{ticket.id}}</a></td>
|
||||
ticket_id=ticket.scoped_id)}}">#{{ticket.scoped_id}}</a></td>
|
||||
<td>{{ ticket.title }}</td>
|
||||
<td>{{ ticket.updated | date }}</td>
|
||||
<td><a href="{{url_for(".ticket_GET",
|
||||
owner="~" + tracker.owner.username,
|
||||
name=tracker.name,
|
||||
ticket_id=ticket.id)}}">{{ ticket.submitter.username }}</a></td>
|
||||
ticket_id=ticket.scoped_id)}}">{{ ticket.submitter.username }}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -11,6 +11,11 @@ class Ticket(Base):
|
|||
tracker_id = sa.Column(sa.Integer, sa.ForeignKey("tracker.id"), nullable=False)
|
||||
tracker = sa.orm.relationship("Tracker", backref=sa.orm.backref("tickets"))
|
||||
|
||||
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 = sa.orm.relationship("Ticket",
|
||||
backref=sa.orm.backref("dupes"),
|
||||
|
|
|
@ -14,6 +14,7 @@ class Tracker(Base):
|
|||
May include slashes to serve as categories (nesting is supported,
|
||||
builds.sr.ht style)
|
||||
"""
|
||||
next_ticket_id = sa.Column(sa.Integer, nullable=False, default=1)
|
||||
|
||||
description = sa.Column(sa.Unicode(8192))
|
||||
"""Markdown"""
|
||||
|
|
Loading…
Reference in New Issue