Add project topics
Adds project topics so that projects may be grouped together. Change-Id: I0216d802ccc0586ffce0182c2c8806d5df54cc2f
This commit is contained in:
parent
37298824ed
commit
cfa725cf1e
35
gertty/alembic/versions/4388de50824a_add_topic_table.py
Normal file
35
gertty/alembic/versions/4388de50824a_add_topic_table.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
"""add topic table
|
||||||
|
|
||||||
|
Revision ID: 4388de50824a
|
||||||
|
Revises: 254ac5fc3941
|
||||||
|
Create Date: 2015-10-31 19:06:38.538948
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4388de50824a'
|
||||||
|
down_revision = '254ac5fc3941'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table('topic',
|
||||||
|
sa.Column('key', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=255), index=True, nullable=False),
|
||||||
|
sa.Column('sequence', sa.Integer(), index=True, unique=True, nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('key')
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table('project_topic',
|
||||||
|
sa.Column('key', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('project_key', sa.Integer(), sa.ForeignKey('project.key'), index=True),
|
||||||
|
sa.Column('topic_key', sa.Integer(), sa.ForeignKey('topic.key'), index=True),
|
||||||
|
sa.Column('sequence', sa.Integer(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('key'),
|
||||||
|
sa.UniqueConstraint('topic_key', 'sequence', name='topic_key_sequence_const'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
pass
|
83
gertty/db.py
83
gertty/db.py
@ -21,7 +21,7 @@ import threading
|
|||||||
import alembic
|
import alembic
|
||||||
import alembic.config
|
import alembic.config
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, Boolean, DateTime, Text
|
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, Boolean, DateTime, Text, UniqueConstraint
|
||||||
from sqlalchemy.schema import ForeignKey
|
from sqlalchemy.schema import ForeignKey
|
||||||
from sqlalchemy.orm import mapper, sessionmaker, relationship, scoped_session
|
from sqlalchemy.orm import mapper, sessionmaker, relationship, scoped_session
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
@ -43,6 +43,20 @@ branch_table = Table(
|
|||||||
Column('project_key', Integer, ForeignKey("project.key"), index=True),
|
Column('project_key', Integer, ForeignKey("project.key"), index=True),
|
||||||
Column('name', String(255), index=True, nullable=False),
|
Column('name', String(255), index=True, nullable=False),
|
||||||
)
|
)
|
||||||
|
topic_table = Table(
|
||||||
|
'topic', metadata,
|
||||||
|
Column('key', Integer, primary_key=True),
|
||||||
|
Column('name', String(255), index=True, nullable=False),
|
||||||
|
Column('sequence', Integer, index=True, unique=True, nullable=False),
|
||||||
|
)
|
||||||
|
project_topic_table = Table(
|
||||||
|
'project_topic', metadata,
|
||||||
|
Column('key', Integer, primary_key=True),
|
||||||
|
Column('project_key', Integer, ForeignKey("project.key"), index=True),
|
||||||
|
Column('topic_key', Integer, ForeignKey("topic.key"), index=True),
|
||||||
|
Column('sequence', Integer, nullable=False),
|
||||||
|
UniqueConstraint('topic_key', 'sequence', name='topic_key_sequence_const'),
|
||||||
|
)
|
||||||
change_table = Table(
|
change_table = Table(
|
||||||
'change', metadata,
|
'change', metadata,
|
||||||
Column('key', Integer, primary_key=True),
|
Column('key', Integer, primary_key=True),
|
||||||
@ -200,6 +214,35 @@ class Branch(object):
|
|||||||
self.project_key = project.key
|
self.project_key = project.key
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
class ProjectTopic(object):
|
||||||
|
def __init__(self, project, topic, sequence):
|
||||||
|
self.project_key = project.key
|
||||||
|
self.topic_key = topic.key
|
||||||
|
self.sequence = sequence
|
||||||
|
|
||||||
|
class Topic(object):
|
||||||
|
def __init__(self, name, sequence):
|
||||||
|
self.name = name
|
||||||
|
self.sequence = sequence
|
||||||
|
|
||||||
|
def addProject(self, project):
|
||||||
|
session = Session.object_session(self)
|
||||||
|
seq = max([x.sequence for x in self.project_topics] + [0])
|
||||||
|
pt = ProjectTopic(project, self, seq+1)
|
||||||
|
self.project_topics.append(pt)
|
||||||
|
self.projects.append(project)
|
||||||
|
session.add(pt)
|
||||||
|
session.flush()
|
||||||
|
|
||||||
|
def removeProject(self, project):
|
||||||
|
session = Session.object_session(self)
|
||||||
|
for pt in self.project_topics:
|
||||||
|
if pt.project_key == project.key:
|
||||||
|
self.project_topics.remove(pt)
|
||||||
|
session.delete(pt)
|
||||||
|
self.projects.remove(project)
|
||||||
|
session.flush()
|
||||||
|
|
||||||
class Change(object):
|
class Change(object):
|
||||||
def __init__(self, project, id, owner, number, branch, change_id,
|
def __init__(self, project, id, owner, number, branch, change_id,
|
||||||
subject, created, updated, status, topic=None,
|
subject, created, updated, status, topic=None,
|
||||||
@ -512,6 +555,10 @@ mapper(Project, project_table, properties=dict(
|
|||||||
changes=relationship(Change, backref='project',
|
changes=relationship(Change, backref='project',
|
||||||
order_by=change_table.c.number,
|
order_by=change_table.c.number,
|
||||||
cascade='all, delete-orphan'),
|
cascade='all, delete-orphan'),
|
||||||
|
topics=relationship(Topic,
|
||||||
|
secondary=project_topic_table,
|
||||||
|
order_by=topic_table.c.name,
|
||||||
|
viewonly=True),
|
||||||
unreviewed_changes=relationship(Change,
|
unreviewed_changes=relationship(Change,
|
||||||
primaryjoin=and_(project_table.c.key==change_table.c.project_key,
|
primaryjoin=and_(project_table.c.key==change_table.c.project_key,
|
||||||
change_table.c.hidden==False,
|
change_table.c.hidden==False,
|
||||||
@ -528,6 +575,14 @@ mapper(Project, project_table, properties=dict(
|
|||||||
),
|
),
|
||||||
))
|
))
|
||||||
mapper(Branch, branch_table)
|
mapper(Branch, branch_table)
|
||||||
|
mapper(Topic, topic_table, properties=dict(
|
||||||
|
projects=relationship(Project,
|
||||||
|
secondary=project_topic_table,
|
||||||
|
order_by=project_table.c.name,
|
||||||
|
viewonly=True),
|
||||||
|
project_topics=relationship(ProjectTopic),
|
||||||
|
))
|
||||||
|
mapper(ProjectTopic, project_topic_table)
|
||||||
mapper(Change, change_table, properties=dict(
|
mapper(Change, change_table, properties=dict(
|
||||||
owner=relationship(Account),
|
owner=relationship(Account),
|
||||||
revisions=relationship(Revision, backref='change',
|
revisions=relationship(Revision, backref='change',
|
||||||
@ -668,20 +723,26 @@ class DatabaseSession(object):
|
|||||||
def vacuum(self):
|
def vacuum(self):
|
||||||
self.session().execute("VACUUM")
|
self.session().execute("VACUUM")
|
||||||
|
|
||||||
def getProjects(self, subscribed=False, unreviewed=False):
|
def getProjects(self, subscribed=False, unreviewed=False, topicless=False):
|
||||||
"""Retrieve projects.
|
"""Retrieve projects.
|
||||||
|
|
||||||
:param subscribed: If True limit to only subscribed projects.
|
:param subscribed: If True limit to only subscribed projects.
|
||||||
:param unreviewed: If True limit to only projects with unreviewed
|
:param unreviewed: If True limit to only projects with unreviewed
|
||||||
changes.
|
changes.
|
||||||
|
:param topicless: If True limit to only projects without topics.
|
||||||
"""
|
"""
|
||||||
query = self.session().query(Project)
|
query = self.session().query(Project)
|
||||||
if subscribed:
|
if subscribed:
|
||||||
query = query.filter_by(subscribed=subscribed)
|
query = query.filter_by(subscribed=subscribed)
|
||||||
if unreviewed:
|
if unreviewed:
|
||||||
query = query.filter(exists().where(Project.unreviewed_changes))
|
query = query.filter(exists().where(Project.unreviewed_changes))
|
||||||
|
if topicless:
|
||||||
|
query = query.filter_by(topics=None)
|
||||||
return query.order_by(Project.name).all()
|
return query.order_by(Project.name).all()
|
||||||
|
|
||||||
|
def getTopics(self):
|
||||||
|
return self.session().query(Topic).order_by(Topic.sequence).all()
|
||||||
|
|
||||||
def getProject(self, key):
|
def getProject(self, key):
|
||||||
try:
|
try:
|
||||||
return self.session().query(Project).filter_by(key=key).one()
|
return self.session().query(Project).filter_by(key=key).one()
|
||||||
@ -694,6 +755,18 @@ class DatabaseSession(object):
|
|||||||
except sqlalchemy.orm.exc.NoResultFound:
|
except sqlalchemy.orm.exc.NoResultFound:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def getTopic(self, key):
|
||||||
|
try:
|
||||||
|
return self.session().query(Topic).filter_by(key=key).one()
|
||||||
|
except sqlalchemy.orm.exc.NoResultFound:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getTopicByName(self, name):
|
||||||
|
try:
|
||||||
|
return self.session().query(Topic).filter_by(name=name).one()
|
||||||
|
except sqlalchemy.orm.exc.NoResultFound:
|
||||||
|
return None
|
||||||
|
|
||||||
def getSyncQueryByName(self, name):
|
def getSyncQueryByName(self, name):
|
||||||
try:
|
try:
|
||||||
return self.session().query(SyncQuery).filter_by(name=name).one()
|
return self.session().query(SyncQuery).filter_by(name=name).one()
|
||||||
@ -872,3 +945,9 @@ class DatabaseSession(object):
|
|||||||
self.session().add(o)
|
self.session().add(o)
|
||||||
self.session().flush()
|
self.session().flush()
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
def createTopic(self, *args, **kw):
|
||||||
|
o = Topic(*args, **kw)
|
||||||
|
self.session().add(o)
|
||||||
|
self.session().flush()
|
||||||
|
return o
|
||||||
|
@ -70,6 +70,12 @@ SORT_BY_REVERSE = 'reverse the sort'
|
|||||||
TOGGLE_LIST_REVIEWED = 'toggle list reviewed'
|
TOGGLE_LIST_REVIEWED = 'toggle list reviewed'
|
||||||
TOGGLE_LIST_SUBSCRIBED = 'toggle list subscribed'
|
TOGGLE_LIST_SUBSCRIBED = 'toggle list subscribed'
|
||||||
TOGGLE_SUBSCRIBED = 'toggle subscribed'
|
TOGGLE_SUBSCRIBED = 'toggle subscribed'
|
||||||
|
NEW_PROJECT_TOPIC = 'new project topic'
|
||||||
|
DELETE_PROJECT_TOPIC = 'delete project topic'
|
||||||
|
MOVE_PROJECT_TOPIC = 'move to project topic'
|
||||||
|
COPY_PROJECT_TOPIC = 'copy to project topic'
|
||||||
|
REMOVE_PROJECT_TOPIC = 'remove from project topic'
|
||||||
|
RENAME_PROJECT_TOPIC = 'rename project topic'
|
||||||
# Diff screens:
|
# Diff screens:
|
||||||
SELECT_PATCHSETS = 'select patchsets'
|
SELECT_PATCHSETS = 'select patchsets'
|
||||||
NEXT_SELECTABLE = 'next selectable'
|
NEXT_SELECTABLE = 'next selectable'
|
||||||
@ -129,6 +135,12 @@ DEFAULT_KEYMAP = {
|
|||||||
TOGGLE_LIST_REVIEWED: 'l',
|
TOGGLE_LIST_REVIEWED: 'l',
|
||||||
TOGGLE_LIST_SUBSCRIBED: 'L',
|
TOGGLE_LIST_SUBSCRIBED: 'L',
|
||||||
TOGGLE_SUBSCRIBED: 's',
|
TOGGLE_SUBSCRIBED: 's',
|
||||||
|
NEW_PROJECT_TOPIC: [['T', 'n']],
|
||||||
|
DELETE_PROJECT_TOPIC: [['T', 'delete']],
|
||||||
|
MOVE_PROJECT_TOPIC: [['T', 'm']],
|
||||||
|
COPY_PROJECT_TOPIC: [['T', 'c']],
|
||||||
|
REMOVE_PROJECT_TOPIC: [['T', 'D']],
|
||||||
|
RENAME_PROJECT_TOPIC: [['T', 'r']],
|
||||||
|
|
||||||
SELECT_PATCHSETS: 'p',
|
SELECT_PATCHSETS: 'p',
|
||||||
NEXT_SELECTABLE: 'tab',
|
NEXT_SELECTABLE: 'tab',
|
||||||
|
@ -148,6 +148,33 @@ class ButtonDialog(urwid.WidgetWrap):
|
|||||||
listbox = urwid.ListBox(rows)
|
listbox = urwid.ListBox(rows)
|
||||||
super(ButtonDialog, self).__init__(urwid.LineBox(listbox, title))
|
super(ButtonDialog, self).__init__(urwid.LineBox(listbox, title))
|
||||||
|
|
||||||
|
class LineEditDialog(ButtonDialog):
|
||||||
|
signals = ['save', 'cancel']
|
||||||
|
def __init__(self, app, title, message, entry_prompt=None,
|
||||||
|
entry_text='', ring=None):
|
||||||
|
self.app = app
|
||||||
|
save_button = FixedButton('Save')
|
||||||
|
cancel_button = FixedButton('Cancel')
|
||||||
|
urwid.connect_signal(save_button, 'click',
|
||||||
|
lambda button:self._emit('save'))
|
||||||
|
urwid.connect_signal(cancel_button, 'click',
|
||||||
|
lambda button:self._emit('cancel'))
|
||||||
|
super(LineEditDialog, self).__init__(title, message, entry_prompt,
|
||||||
|
entry_text,
|
||||||
|
buttons=[save_button,
|
||||||
|
cancel_button],
|
||||||
|
ring=ring)
|
||||||
|
|
||||||
|
def keypress(self, size, key):
|
||||||
|
if not self.app.input_buffer:
|
||||||
|
key = super(LineEditDialog, self).keypress(size, key)
|
||||||
|
keys = self.app.input_buffer + [key]
|
||||||
|
commands = self.app.config.keymap.getCommands(keys)
|
||||||
|
if keymap.ACTIVATE in commands:
|
||||||
|
self._emit('save')
|
||||||
|
return None
|
||||||
|
return key
|
||||||
|
|
||||||
class TextEditDialog(urwid.WidgetWrap):
|
class TextEditDialog(urwid.WidgetWrap):
|
||||||
signals = ['save', 'cancel']
|
signals = ['save', 'cancel']
|
||||||
def __init__(self, title, prompt, button, text, ring=None):
|
def __init__(self, title, prompt, button, text, ring=None):
|
||||||
|
@ -17,6 +17,7 @@ import logging
|
|||||||
import urwid
|
import urwid
|
||||||
|
|
||||||
from gertty import keymap
|
from gertty import keymap
|
||||||
|
from gertty import mywid
|
||||||
from gertty import sync
|
from gertty import sync
|
||||||
from gertty.view import change_list as view_change_list
|
from gertty.view import change_list as view_change_list
|
||||||
from gertty.view import mouse_scroll_decorator
|
from gertty.view import mouse_scroll_decorator
|
||||||
@ -31,10 +32,14 @@ class ProjectRow(urwid.Button):
|
|||||||
def selectable(self):
|
def selectable(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __init__(self, project, callback=None):
|
def __init__(self, project, topic, callback=None):
|
||||||
super(ProjectRow, self).__init__('', on_press=callback,
|
super(ProjectRow, self).__init__('', on_press=callback,
|
||||||
user_data=(project.key, project.name))
|
user_data=(project.key, project.name))
|
||||||
self.project_key = project.key
|
self.project_key = project.key
|
||||||
|
if topic:
|
||||||
|
self.topic_key = topic.key
|
||||||
|
else:
|
||||||
|
self.topic_key = None
|
||||||
name = urwid.Text(project.name)
|
name = urwid.Text(project.name)
|
||||||
name.set_wrap_mode('clip')
|
name.set_wrap_mode('clip')
|
||||||
self.unreviewed_changes = urwid.Text(u'', align=urwid.RIGHT)
|
self.unreviewed_changes = urwid.Text(u'', align=urwid.RIGHT)
|
||||||
@ -60,6 +65,47 @@ class ProjectRow(urwid.Button):
|
|||||||
self.unreviewed_changes.set_text('%i ' % len(project.unreviewed_changes))
|
self.unreviewed_changes.set_text('%i ' % len(project.unreviewed_changes))
|
||||||
self.open_changes.set_text('%i ' % len(project.open_changes))
|
self.open_changes.set_text('%i ' % len(project.open_changes))
|
||||||
|
|
||||||
|
class TopicRow(urwid.Button):
|
||||||
|
project_focus_map = {None: 'focused',
|
||||||
|
'subscribed-project': 'focused-subscribed-project',
|
||||||
|
}
|
||||||
|
|
||||||
|
def selectable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _setName(self, name):
|
||||||
|
self.name.set_text('[[ '+name+' ]]')
|
||||||
|
|
||||||
|
def __init__(self, topic, callback=None):
|
||||||
|
super(TopicRow, self).__init__('', on_press=callback,
|
||||||
|
user_data=(topic.key, topic.name))
|
||||||
|
self.topic_key = topic.key
|
||||||
|
self.name = urwid.Text('')
|
||||||
|
self._setName(topic.name)
|
||||||
|
self.name.set_wrap_mode('clip')
|
||||||
|
self.unreviewed_changes = urwid.Text(u'', align=urwid.RIGHT)
|
||||||
|
self.open_changes = urwid.Text(u'', align=urwid.RIGHT)
|
||||||
|
col = urwid.Columns([
|
||||||
|
self.name,
|
||||||
|
('fixed', 11, self.unreviewed_changes),
|
||||||
|
('fixed', 5, self.open_changes),
|
||||||
|
])
|
||||||
|
self.row_style = urwid.AttrMap(col, '')
|
||||||
|
self._w = urwid.AttrMap(self.row_style, None, focus_map=self.project_focus_map)
|
||||||
|
self.row_style.set_attr_map({None: 'subscribed-project'})
|
||||||
|
self.update(topic)
|
||||||
|
|
||||||
|
def update(self, topic, unreviewed_changes=None, open_changes=None):
|
||||||
|
self._setName(topic.name)
|
||||||
|
if unreviewed_changes is None:
|
||||||
|
self.unreviewed_changes.set_text('')
|
||||||
|
else:
|
||||||
|
self.unreviewed_changes.set_text('%i ' % unreviewed_changes)
|
||||||
|
if open_changes is None:
|
||||||
|
self.open_changes.set_text('')
|
||||||
|
else:
|
||||||
|
self.open_changes.set_text('%i ' % open_changes)
|
||||||
|
|
||||||
class ProjectListHeader(urwid.WidgetWrap):
|
class ProjectListHeader(urwid.WidgetWrap):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
cols = [urwid.Text(u'Project'),
|
cols = [urwid.Text(u'Project'),
|
||||||
@ -79,7 +125,19 @@ class ProjectListView(urwid.WidgetWrap):
|
|||||||
(key(keymap.TOGGLE_SUBSCRIBED),
|
(key(keymap.TOGGLE_SUBSCRIBED),
|
||||||
"Toggle the subscription flag for the currently selected project"),
|
"Toggle the subscription flag for the currently selected project"),
|
||||||
(key(keymap.REFRESH),
|
(key(keymap.REFRESH),
|
||||||
"Sync subscribed projects")
|
"Sync subscribed projects"),
|
||||||
|
(key(keymap.NEW_PROJECT_TOPIC),
|
||||||
|
"Create project topic"),
|
||||||
|
(key(keymap.DELETE_PROJECT_TOPIC),
|
||||||
|
"Delete selected project topic"),
|
||||||
|
(key(keymap.MOVE_PROJECT_TOPIC),
|
||||||
|
"Move selected project to topic"),
|
||||||
|
(key(keymap.COPY_PROJECT_TOPIC),
|
||||||
|
"Copy selected project to topic"),
|
||||||
|
(key(keymap.REMOVE_PROJECT_TOPIC),
|
||||||
|
"Remove selected project from topic"),
|
||||||
|
(key(keymap.RENAME_PROJECT_TOPIC),
|
||||||
|
"Rename selected project topic"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
@ -89,6 +147,8 @@ class ProjectListView(urwid.WidgetWrap):
|
|||||||
self.unreviewed = True
|
self.unreviewed = True
|
||||||
self.subscribed = True
|
self.subscribed = True
|
||||||
self.project_rows = {}
|
self.project_rows = {}
|
||||||
|
self.topic_rows = {}
|
||||||
|
self.open_topics = set()
|
||||||
self.listbox = urwid.ListBox(urwid.SimpleFocusListWalker([]))
|
self.listbox = urwid.ListBox(urwid.SimpleFocusListWalker([]))
|
||||||
self.header = ProjectListHeader()
|
self.header = ProjectListHeader()
|
||||||
self.refresh()
|
self.refresh()
|
||||||
@ -110,6 +170,56 @@ class ProjectListView(urwid.WidgetWrap):
|
|||||||
self.log.debug("Refreshing project list due to event %s" % (event,))
|
self.log.debug("Refreshing project list due to event %s" % (event,))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _deleteRow(self, row):
|
||||||
|
if row in self.listbox.body:
|
||||||
|
self.listbox.body.remove(row)
|
||||||
|
if isinstance(row, ProjectRow):
|
||||||
|
del self.project_rows[(row.topic_key, row.project_key)]
|
||||||
|
else:
|
||||||
|
del self.topic_rows[row.topic_key]
|
||||||
|
|
||||||
|
def _projectRow(self, i, project, topic):
|
||||||
|
# Ensure that the row at i is the given project. If the row
|
||||||
|
# already exists somewhere in the list, delete all rows
|
||||||
|
# between i and the row and then update the row. If the row
|
||||||
|
# does not exist, insert the row at position i.
|
||||||
|
topic_key = topic and topic.key or None
|
||||||
|
key = (topic_key, project.key)
|
||||||
|
row = self.project_rows.get(key)
|
||||||
|
while row: # This is "if row: while True:".
|
||||||
|
if i >= len(self.listbox.body):
|
||||||
|
break
|
||||||
|
current_row = self.listbox.body[i]
|
||||||
|
if (isinstance(current_row, ProjectRow) and
|
||||||
|
current_row.project_key == project.key):
|
||||||
|
break
|
||||||
|
self._deleteRow(current_row)
|
||||||
|
if not row:
|
||||||
|
row = ProjectRow(project, topic, self.onSelect)
|
||||||
|
self.listbox.body.insert(i, row)
|
||||||
|
self.project_rows[key] = row
|
||||||
|
else:
|
||||||
|
row.update(project)
|
||||||
|
return i+1
|
||||||
|
|
||||||
|
def _topicRow(self, i, topic):
|
||||||
|
row = self.topic_rows.get(topic.key)
|
||||||
|
while row: # This is "if row: while True:".
|
||||||
|
if i >= len(self.listbox.body):
|
||||||
|
break
|
||||||
|
current_row = self.listbox.body[i]
|
||||||
|
if (isinstance(current_row, TopicRow) and
|
||||||
|
current_row.topic_key == topic.key):
|
||||||
|
break
|
||||||
|
self._deleteRow(current_row)
|
||||||
|
if not row:
|
||||||
|
row = TopicRow(topic, self.onSelectTopic)
|
||||||
|
self.listbox.body.insert(i, row)
|
||||||
|
self.topic_rows[topic.key] = row
|
||||||
|
else:
|
||||||
|
row.update(topic)
|
||||||
|
return i + 1
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
if self.subscribed:
|
if self.subscribed:
|
||||||
self.title = u'Subscribed projects'
|
self.title = u'Subscribed projects'
|
||||||
@ -118,24 +228,33 @@ class ProjectListView(urwid.WidgetWrap):
|
|||||||
else:
|
else:
|
||||||
self.title = u'All projects'
|
self.title = u'All projects'
|
||||||
self.app.status.update(title=self.title)
|
self.app.status.update(title=self.title)
|
||||||
unseen_keys = set(self.project_rows.keys())
|
|
||||||
with self.app.db.getSession() as session:
|
with self.app.db.getSession() as session:
|
||||||
i = 0
|
i = 0
|
||||||
for project in session.getProjects(
|
for project in session.getProjects(topicless=True,
|
||||||
subscribed=self.subscribed, unreviewed=self.unreviewed):
|
subscribed=self.subscribed, unreviewed=self.unreviewed):
|
||||||
row = self.project_rows.get(project.key)
|
#self.log.debug("project: %s" % project.name)
|
||||||
if not row:
|
i = self._projectRow(i, project, None)
|
||||||
row = ProjectRow(project, self.onSelect)
|
for topic in session.getTopics():
|
||||||
self.listbox.body.insert(i, row)
|
#self.log.debug("topic: %s" % topic.name)
|
||||||
self.project_rows[project.key] = row
|
i = self._topicRow(i, topic)
|
||||||
else:
|
topic_unreviewed = 0
|
||||||
row.update(project)
|
topic_open = 0
|
||||||
unseen_keys.remove(project.key)
|
for project in topic.projects:
|
||||||
i += 1
|
#self.log.debug(" project: %s" % project.name)
|
||||||
for key in unseen_keys:
|
topic_unreviewed += len(project.unreviewed_changes)
|
||||||
row = self.project_rows[key]
|
topic_open += len(project.open_changes)
|
||||||
self.listbox.body.remove(row)
|
if self.subscribed:
|
||||||
del self.project_rows[key]
|
if not project.subscribed:
|
||||||
|
continue
|
||||||
|
if self.unreviewed and not project.unreviewed_changes:
|
||||||
|
continue
|
||||||
|
if topic.key in self.open_topics:
|
||||||
|
i = self._projectRow(i, project, topic)
|
||||||
|
topic_row = self.topic_rows.get(topic.key)
|
||||||
|
topic_row.update(topic, topic_unreviewed, topic_open)
|
||||||
|
while i < len(self.listbox.body):
|
||||||
|
current_row = self.listbox.body[i]
|
||||||
|
self._deleteRow(current_row)
|
||||||
|
|
||||||
def toggleSubscribed(self, project_key):
|
def toggleSubscribed(self, project_key):
|
||||||
with self.app.db.getSession() as session:
|
with self.app.db.getSession() as session:
|
||||||
@ -151,6 +270,126 @@ class ProjectListView(urwid.WidgetWrap):
|
|||||||
"_project_key:%s %s" % (project_key, self.app.config.project_change_list_query),
|
"_project_key:%s %s" % (project_key, self.app.config.project_change_list_query),
|
||||||
project_name, project_key=project_key, unreviewed=True))
|
project_name, project_key=project_key, unreviewed=True))
|
||||||
|
|
||||||
|
def onSelectTopic(self, button, data):
|
||||||
|
topic_key = data[0]
|
||||||
|
self.open_topics ^= set([topic_key])
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def createTopic(self):
|
||||||
|
dialog = mywid.LineEditDialog(self.app, 'Topic', 'Create a new topic.',
|
||||||
|
'Topic: ', '', self.app.ring)
|
||||||
|
urwid.connect_signal(dialog, 'save',
|
||||||
|
lambda button: self.closeCreateTopic(dialog, True))
|
||||||
|
urwid.connect_signal(dialog, 'cancel',
|
||||||
|
lambda button: self.closeCreateTopic(dialog, False))
|
||||||
|
self.app.popup(dialog)
|
||||||
|
|
||||||
|
def closeCreateTopic(self, dialog, save):
|
||||||
|
if save:
|
||||||
|
last_topic_key = None
|
||||||
|
for row in self.listbox.body:
|
||||||
|
if isinstance(row, TopicRow):
|
||||||
|
last_topic_key = row.topic_key
|
||||||
|
with self.app.db.getSession() as session:
|
||||||
|
if last_topic_key:
|
||||||
|
last_topic = session.getTopic(last_topic_key)
|
||||||
|
seq = last_topic.sequence + 1
|
||||||
|
else:
|
||||||
|
seq = 0
|
||||||
|
t = session.createTopic(dialog.entry.edit_text, seq)
|
||||||
|
self.app.backScreen()
|
||||||
|
|
||||||
|
def deleteTopic(self):
|
||||||
|
pos = self.listbox.focus_position
|
||||||
|
row = self.listbox.body[pos]
|
||||||
|
if not isinstance(row, TopicRow):
|
||||||
|
return
|
||||||
|
with self.app.db.getSession() as session:
|
||||||
|
topic = session.getTopic(row.topic_key)
|
||||||
|
session.delete(topic)
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def renameTopic(self):
|
||||||
|
pos = self.listbox.focus_position
|
||||||
|
row = self.listbox.body[pos]
|
||||||
|
if not isinstance(row, TopicRow):
|
||||||
|
return
|
||||||
|
with self.app.db.getSession() as session:
|
||||||
|
topic = session.getTopic(row.topic_key)
|
||||||
|
name = topic.name
|
||||||
|
key = topic.key
|
||||||
|
dialog = mywid.LineEditDialog(self.app, 'Topic', 'Rename a new topic.',
|
||||||
|
'Topic: ', name, self.app.ring)
|
||||||
|
urwid.connect_signal(dialog, 'save',
|
||||||
|
lambda button: self.closeRenameTopic(dialog, True, key))
|
||||||
|
urwid.connect_signal(dialog, 'cancel',
|
||||||
|
lambda button: self.closeRenameTopic(dialog, False, key))
|
||||||
|
self.app.popup(dialog)
|
||||||
|
|
||||||
|
def closeRenameTopic(self, dialog, save, key):
|
||||||
|
if save:
|
||||||
|
with self.app.db.getSession() as session:
|
||||||
|
topic = session.getTopic(key)
|
||||||
|
topic.name = dialog.entry.edit_text
|
||||||
|
self.app.backScreen()
|
||||||
|
|
||||||
|
def copyMoveToTopic(self, move):
|
||||||
|
if move:
|
||||||
|
verb = 'Move'
|
||||||
|
else:
|
||||||
|
verb = 'Copy'
|
||||||
|
pos = self.listbox.focus_position
|
||||||
|
row = self.listbox.body[pos]
|
||||||
|
if not isinstance(row, ProjectRow):
|
||||||
|
return
|
||||||
|
dialog = mywid.LineEditDialog(self.app, 'Topic', '%s to topic.' % verb,
|
||||||
|
'Topic: ', '', self.app.ring)
|
||||||
|
urwid.connect_signal(dialog, 'save',
|
||||||
|
lambda button: self.closeCopyMoveToTopic(dialog, True, row, move))
|
||||||
|
urwid.connect_signal(dialog, 'cancel',
|
||||||
|
lambda button: self.closeCopyMoveToTopic(dialog, False, row, move))
|
||||||
|
self.app.popup(dialog)
|
||||||
|
|
||||||
|
def closeCopyMoveToTopic(self, dialog, save, row, move):
|
||||||
|
error = None
|
||||||
|
if save:
|
||||||
|
with self.app.db.getSession() as session:
|
||||||
|
project = session.getProject(row.project_key)
|
||||||
|
topic_name = dialog.entry.edit_text
|
||||||
|
new_topic = session.getTopicByName(topic_name)
|
||||||
|
if not new_topic:
|
||||||
|
error = "Unable to find topic %s" % topic_name
|
||||||
|
else:
|
||||||
|
if move and row.topic_key:
|
||||||
|
old_topic = session.getTopic(row.topic_key)
|
||||||
|
self.log.debug("Remove %s from %s" % (project, old_topic))
|
||||||
|
old_topic.removeProject(project)
|
||||||
|
self.log.debug("Add %s to %s" % (project, new_topic))
|
||||||
|
new_topic.addProject(project)
|
||||||
|
self.app.backScreen()
|
||||||
|
if error:
|
||||||
|
self.app.error(error)
|
||||||
|
|
||||||
|
def moveToTopic(self):
|
||||||
|
self.copyMoveToTopic(True)
|
||||||
|
|
||||||
|
def copyToTopic(self):
|
||||||
|
self.copyMoveToTopic(False)
|
||||||
|
|
||||||
|
def removeFromTopic(self):
|
||||||
|
pos = self.listbox.focus_position
|
||||||
|
row = self.listbox.body[pos]
|
||||||
|
if not isinstance(row, ProjectRow):
|
||||||
|
return
|
||||||
|
if not row.topic_key:
|
||||||
|
return
|
||||||
|
with self.app.db.getSession() as session:
|
||||||
|
project = session.getProject(row.project_key)
|
||||||
|
topic = session.getTopic(row.topic_key)
|
||||||
|
self.log.debug("Remove %s from %s" % (project, topic))
|
||||||
|
topic.removeProject(project)
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
def keypress(self, size, key):
|
def keypress(self, size, key):
|
||||||
if not self.app.input_buffer:
|
if not self.app.input_buffer:
|
||||||
key = super(ProjectListView, self).keypress(size, key)
|
key = super(ProjectListView, self).keypress(size, key)
|
||||||
@ -182,6 +421,24 @@ class ProjectListView(urwid.WidgetWrap):
|
|||||||
if subscribed:
|
if subscribed:
|
||||||
self.app.sync.submitTask(sync.SyncProjectTask(project_key))
|
self.app.sync.submitTask(sync.SyncProjectTask(project_key))
|
||||||
return None
|
return None
|
||||||
|
if keymap.NEW_PROJECT_TOPIC in commands:
|
||||||
|
self.createTopic()
|
||||||
|
return None
|
||||||
|
if keymap.DELETE_PROJECT_TOPIC in commands:
|
||||||
|
self.deleteTopic()
|
||||||
|
return None
|
||||||
|
if keymap.COPY_PROJECT_TOPIC in commands:
|
||||||
|
self.copyToTopic()
|
||||||
|
return None
|
||||||
|
if keymap.MOVE_PROJECT_TOPIC in commands:
|
||||||
|
self.moveToTopic()
|
||||||
|
return None
|
||||||
|
if keymap.REMOVE_PROJECT_TOPIC in commands:
|
||||||
|
self.removeFromTopic()
|
||||||
|
return None
|
||||||
|
if keymap.RENAME_PROJECT_TOPIC in commands:
|
||||||
|
self.renameTopic()
|
||||||
|
return None
|
||||||
if keymap.REFRESH in commands:
|
if keymap.REFRESH in commands:
|
||||||
self.app.sync.submitTask(
|
self.app.sync.submitTask(
|
||||||
sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY))
|
sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user