forked from openlp/openlp
Add support for the Author type property
This commit is contained in:
parent
fbf1183419
commit
71b34578d7
@ -194,6 +194,7 @@ class Manager(object):
|
|||||||
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
||||||
except (SQLAlchemyError, DBAPIError):
|
except (SQLAlchemyError, DBAPIError):
|
||||||
log.exception('Error loading database: %s', self.db_url)
|
log.exception('Error loading database: %s', self.db_url)
|
||||||
|
return
|
||||||
if db_ver > up_ver:
|
if db_ver > up_ver:
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
translate('OpenLP.Manager', 'Database Error'),
|
translate('OpenLP.Manager', 'Database Error'),
|
||||||
@ -215,7 +216,7 @@ class Manager(object):
|
|||||||
Save an object to the database
|
Save an object to the database
|
||||||
|
|
||||||
:param object_instance: The object to save
|
:param object_instance: The object to save
|
||||||
:param commit: Commit the session with this object
|
:param commit: Commit the session with this object
|
||||||
"""
|
"""
|
||||||
for try_count in range(3):
|
for try_count in range(3):
|
||||||
try:
|
try:
|
||||||
|
@ -122,6 +122,11 @@ class Ui_EditSongDialog(object):
|
|||||||
self.author_add_layout.setObjectName('author_add_layout')
|
self.author_add_layout.setObjectName('author_add_layout')
|
||||||
self.authors_combo_box = create_combo_box(self.authors_group_box, 'authors_combo_box')
|
self.authors_combo_box = create_combo_box(self.authors_group_box, 'authors_combo_box')
|
||||||
self.author_add_layout.addWidget(self.authors_combo_box)
|
self.author_add_layout.addWidget(self.authors_combo_box)
|
||||||
|
self.author_types_combo_box = create_combo_box(self.authors_group_box, 'author_types_combo_box', editable=False)
|
||||||
|
# Need to give these boxes some min width, else they are too small
|
||||||
|
self.authors_combo_box.setMinimumWidth(150)
|
||||||
|
self.author_types_combo_box.setMinimumWidth(80)
|
||||||
|
self.author_add_layout.addWidget(self.author_types_combo_box)
|
||||||
self.author_add_button = QtGui.QPushButton(self.authors_group_box)
|
self.author_add_button = QtGui.QPushButton(self.authors_group_box)
|
||||||
self.author_add_button.setObjectName('author_add_button')
|
self.author_add_button.setObjectName('author_add_button')
|
||||||
self.author_add_layout.addWidget(self.author_add_button)
|
self.author_add_layout.addWidget(self.author_add_button)
|
||||||
@ -330,7 +335,7 @@ class Ui_EditSongDialog(object):
|
|||||||
translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> You have not entered a verse order.')
|
translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> You have not entered a verse order.')
|
||||||
|
|
||||||
|
|
||||||
def create_combo_box(parent, name):
|
def create_combo_box(parent, name, editable=True):
|
||||||
"""
|
"""
|
||||||
Utility method to generate a standard combo box for this dialog.
|
Utility method to generate a standard combo box for this dialog.
|
||||||
|
|
||||||
@ -340,7 +345,7 @@ def create_combo_box(parent, name):
|
|||||||
combo_box = QtGui.QComboBox(parent)
|
combo_box = QtGui.QComboBox(parent)
|
||||||
combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToMinimumContentsLength)
|
combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToMinimumContentsLength)
|
||||||
combo_box.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
|
combo_box.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
|
||||||
combo_box.setEditable(True)
|
combo_box.setEditable(editable)
|
||||||
combo_box.setInsertPolicy(QtGui.QComboBox.NoInsert)
|
combo_box.setInsertPolicy(QtGui.QComboBox.NoInsert)
|
||||||
combo_box.setObjectName(name)
|
combo_box.setObjectName(name)
|
||||||
return combo_box
|
return combo_box
|
||||||
|
@ -42,7 +42,7 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStri
|
|||||||
from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list
|
from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list
|
||||||
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
|
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
|
||||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||||
from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile
|
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorSong, Topic, MediaFile
|
||||||
from openlp.plugins.songs.lib.ui import SongStrings
|
from openlp.plugins.songs.lib.ui import SongStrings
|
||||||
from openlp.plugins.songs.lib.xml import SongXML
|
from openlp.plugins.songs.lib.xml import SongXML
|
||||||
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
|
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
|
||||||
@ -122,12 +122,12 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
combo.setItemData(row, obj.id)
|
combo.setItemData(row, obj.id)
|
||||||
set_case_insensitive_completer(cache, combo)
|
set_case_insensitive_completer(cache, combo)
|
||||||
|
|
||||||
def _add_author_to_list(self, author):
|
def _add_author_to_list(self, author, author_type):
|
||||||
"""
|
"""
|
||||||
Add an author to the author list.
|
Add an author to the author list.
|
||||||
"""
|
"""
|
||||||
author_item = QtGui.QListWidgetItem(str(author.display_name))
|
author_item = QtGui.QListWidgetItem(author.get_display_name(author_type))
|
||||||
author_item.setData(QtCore.Qt.UserRole, author.id)
|
author_item.setData(QtCore.Qt.UserRole, (author.id, author_type))
|
||||||
self.authors_list_view.addItem(author_item)
|
self.authors_list_view.addItem(author_item)
|
||||||
|
|
||||||
def _extract_verse_order(self, verse_order):
|
def _extract_verse_order(self, verse_order):
|
||||||
@ -302,6 +302,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
self.authors.append(author.display_name)
|
self.authors.append(author.display_name)
|
||||||
set_case_insensitive_completer(self.authors, self.authors_combo_box)
|
set_case_insensitive_completer(self.authors, self.authors_combo_box)
|
||||||
|
|
||||||
|
#Types
|
||||||
|
self.author_types_combo_box.clear()
|
||||||
|
self.author_types_combo_box.addItem('')
|
||||||
|
# Don't iterate over the dictionary to give them this specific order
|
||||||
|
self.author_types_combo_box.addItem(Author.Types[Author.TYPE_WORDS], Author.TYPE_WORDS)
|
||||||
|
self.author_types_combo_box.addItem(Author.Types[Author.TYPE_MUSIC], Author.TYPE_MUSIC)
|
||||||
|
self.author_types_combo_box.addItem(Author.Types[Author.TYPE_TRANSLATION], Author.TYPE_TRANSLATION)
|
||||||
|
|
||||||
def load_topics(self):
|
def load_topics(self):
|
||||||
"""
|
"""
|
||||||
Load the topics into the combobox.
|
Load the topics into the combobox.
|
||||||
@ -454,10 +462,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
self.tag_rows()
|
self.tag_rows()
|
||||||
# clear the results
|
# clear the results
|
||||||
self.authors_list_view.clear()
|
self.authors_list_view.clear()
|
||||||
for author in self.song.authors:
|
for author_song in self.song.authors_songs:
|
||||||
author_name = QtGui.QListWidgetItem(str(author.display_name))
|
self._add_author_to_list(author_song.author, author_song.author_type)
|
||||||
author_name.setData(QtCore.Qt.UserRole, author.id)
|
|
||||||
self.authors_list_view.addItem(author_name)
|
|
||||||
# clear the results
|
# clear the results
|
||||||
self.topics_list_view.clear()
|
self.topics_list_view.clear()
|
||||||
for topic in self.song.topics:
|
for topic in self.song.topics:
|
||||||
@ -496,6 +502,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
item = int(self.authors_combo_box.currentIndex())
|
item = int(self.authors_combo_box.currentIndex())
|
||||||
text = self.authors_combo_box.currentText().strip(' \r\n\t')
|
text = self.authors_combo_box.currentText().strip(' \r\n\t')
|
||||||
|
author_type = self.author_types_combo_box.itemData(self.author_types_combo_box.currentIndex())
|
||||||
# This if statement is for OS X, which doesn't seem to work well with
|
# This if statement is for OS X, which doesn't seem to work well with
|
||||||
# the QCompleter auto-completion class. See bug #812628.
|
# the QCompleter auto-completion class. See bug #812628.
|
||||||
if text in self.authors:
|
if text in self.authors:
|
||||||
@ -513,7 +520,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
author = Author.populate(first_name=text.rsplit(' ', 1)[0], last_name=text.rsplit(' ', 1)[1],
|
author = Author.populate(first_name=text.rsplit(' ', 1)[0], last_name=text.rsplit(' ', 1)[1],
|
||||||
display_name=text)
|
display_name=text)
|
||||||
self.manager.save_object(author)
|
self.manager.save_object(author)
|
||||||
self._add_author_to_list(author)
|
self._add_author_to_list(author, author_type)
|
||||||
self.load_authors()
|
self.load_authors()
|
||||||
self.authors_combo_box.setCurrentIndex(0)
|
self.authors_combo_box.setCurrentIndex(0)
|
||||||
else:
|
else:
|
||||||
@ -525,7 +532,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
message=translate('SongsPlugin.EditSongForm', 'This author is already in the list.'))
|
message=translate('SongsPlugin.EditSongForm', 'This author is already in the list.'))
|
||||||
else:
|
else:
|
||||||
self._add_author_to_list(author)
|
self._add_author_to_list(author, author_type)
|
||||||
self.authors_combo_box.setCurrentIndex(0)
|
self.authors_combo_box.setCurrentIndex(0)
|
||||||
else:
|
else:
|
||||||
QtGui.QMessageBox.warning(
|
QtGui.QMessageBox.warning(
|
||||||
@ -538,8 +545,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Run a set of actions when an author in the list is selected (mainly enable the delete button).
|
Run a set of actions when an author in the list is selected (mainly enable the delete button).
|
||||||
"""
|
"""
|
||||||
if self.authors_list_view.count() > 1:
|
self.author_remove_button.setEnabled(True)
|
||||||
self.author_remove_button.setEnabled(True)
|
|
||||||
|
|
||||||
def on_author_remove_button_clicked(self):
|
def on_author_remove_button_clicked(self):
|
||||||
"""
|
"""
|
||||||
@ -906,13 +912,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||||||
else:
|
else:
|
||||||
self.song.theme_name = None
|
self.song.theme_name = None
|
||||||
self._process_lyrics()
|
self._process_lyrics()
|
||||||
self.song.authors = []
|
self.song.authors_songs = []
|
||||||
for row in range(self.authors_list_view.count()):
|
for row in range(self.authors_list_view.count()):
|
||||||
item = self.authors_list_view.item(row)
|
item = self.authors_list_view.item(row)
|
||||||
author_id = (item.data(QtCore.Qt.UserRole))
|
author_song = AuthorSong()
|
||||||
author = self.manager.get_object(Author, author_id)
|
author_song.author_id = item.data(QtCore.Qt.UserRole)[0]
|
||||||
if author is not None:
|
author_song.author_type = item.data(QtCore.Qt.UserRole)[1]
|
||||||
self.song.authors.append(author)
|
self.song.authors_songs.append(author_song)
|
||||||
self.song.topics = []
|
self.song.topics = []
|
||||||
for row in range(self.topics_list_view.count()):
|
for row in range(self.topics_list_view.count()):
|
||||||
item = self.topics_list_view.item(row)
|
item = self.topics_list_view.item(row)
|
||||||
|
@ -389,13 +389,6 @@ def clean_song(manager, song):
|
|||||||
song.lyrics = str(song.lyrics, encoding='utf8')
|
song.lyrics = str(song.lyrics, encoding='utf8')
|
||||||
verses = SongXML().get_verses(song.lyrics)
|
verses = SongXML().get_verses(song.lyrics)
|
||||||
song.search_lyrics = ' '.join([clean_string(verse[1]) for verse in verses])
|
song.search_lyrics = ' '.join([clean_string(verse[1]) for verse in verses])
|
||||||
# The song does not have any author, add one.
|
|
||||||
if not song.authors:
|
|
||||||
name = SongStrings.AuthorUnknown
|
|
||||||
author = manager.get_object_filtered(Author, Author.display_name == name)
|
|
||||||
if author is None:
|
|
||||||
author = Author.populate(display_name=name, last_name='', first_name='')
|
|
||||||
song.authors.append(author)
|
|
||||||
if song.copyright:
|
if song.copyright:
|
||||||
song.copyright = CONTROL_CHARS.sub('', song.copyright).strip()
|
song.copyright = CONTROL_CHARS.sub('', song.copyright).strip()
|
||||||
|
|
||||||
|
@ -39,12 +39,34 @@ from sqlalchemy.sql.expression import func
|
|||||||
|
|
||||||
from openlp.core.lib.db import BaseModel, init_db
|
from openlp.core.lib.db import BaseModel, init_db
|
||||||
from openlp.core.utils import get_natural_key
|
from openlp.core.utils import get_natural_key
|
||||||
|
from openlp.core.lib import translate
|
||||||
|
|
||||||
|
|
||||||
class Author(BaseModel):
|
class Author(BaseModel):
|
||||||
"""
|
"""
|
||||||
Author model
|
Author model
|
||||||
"""
|
"""
|
||||||
|
#These types are defined by OpenLyrics: http://openlyrics.info/dataformat.html#authors
|
||||||
|
TYPE_WORDS = 'words'
|
||||||
|
TYPE_MUSIC = 'music'
|
||||||
|
TYPE_TRANSLATION = 'translation'
|
||||||
|
Types = {
|
||||||
|
TYPE_WORDS: translate('OpenLP.Ui', 'Words'),
|
||||||
|
TYPE_MUSIC: translate('OpenLP.Ui', 'Music'),
|
||||||
|
TYPE_TRANSLATION: translate('OpenLP.Ui', 'Translation')
|
||||||
|
}
|
||||||
|
def get_display_name(self, author_type=None):
|
||||||
|
if author_type:
|
||||||
|
return "%s: %s"%(self.Types[author_type], self.display_name)
|
||||||
|
return self.display_name
|
||||||
|
|
||||||
|
class AuthorSong(BaseModel):
|
||||||
|
"""
|
||||||
|
Relationship between Authors and Songs (many to many).
|
||||||
|
Need to define this relationship table explicit to get access to the
|
||||||
|
Association Object (author_type).
|
||||||
|
http://docs.sqlalchemy.org/en/latest/orm/relationships.html#association-object
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -67,6 +89,7 @@ class Song(BaseModel):
|
|||||||
"""
|
"""
|
||||||
Song model
|
Song model
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sort_key = []
|
self.sort_key = []
|
||||||
|
|
||||||
@ -120,6 +143,7 @@ def init_schema(url):
|
|||||||
|
|
||||||
* author_id
|
* author_id
|
||||||
* song_id
|
* song_id
|
||||||
|
* author_type
|
||||||
|
|
||||||
**media_files Table**
|
**media_files Table**
|
||||||
* id
|
* id
|
||||||
@ -230,7 +254,8 @@ def init_schema(url):
|
|||||||
authors_songs_table = Table(
|
authors_songs_table = Table(
|
||||||
'authors_songs', metadata,
|
'authors_songs', metadata,
|
||||||
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
||||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True)
|
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||||
|
Column('author_type', types.String(), primary_key=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Definition of the "songs_topics" table
|
# Definition of the "songs_topics" table
|
||||||
@ -241,10 +266,14 @@ def init_schema(url):
|
|||||||
)
|
)
|
||||||
|
|
||||||
mapper(Author, authors_table)
|
mapper(Author, authors_table)
|
||||||
|
mapper(AuthorSong, authors_songs_table, properties={
|
||||||
|
'author': relation(Author)
|
||||||
|
})
|
||||||
mapper(Book, song_books_table)
|
mapper(Book, song_books_table)
|
||||||
mapper(MediaFile, media_files_table)
|
mapper(MediaFile, media_files_table)
|
||||||
mapper(Song, songs_table, properties={
|
mapper(Song, songs_table, properties={
|
||||||
'authors': relation(Author, backref='songs', secondary=authors_songs_table, lazy=False),
|
'authors_songs': relation(AuthorSong, cascade="all, delete-orphan"),
|
||||||
|
'authors': relation(Author, secondary=authors_songs_table, viewonly=True),
|
||||||
'book': relation(Book, backref='songs'),
|
'book': relation(Book, backref='songs'),
|
||||||
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
|
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
|
||||||
'topics': relation(Topic, backref='songs', secondary=songs_topics_table)
|
'topics': relation(Topic, backref='songs', secondary=songs_topics_table)
|
||||||
|
@ -464,23 +464,45 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
def generate_footer(self, item, song):
|
def generate_footer(self, item, song):
|
||||||
"""
|
"""
|
||||||
Generates the song footer based on a song and adds details to a service item.
|
Generates the song footer based on a song and adds details to a service item.
|
||||||
author_list is only required for initial song generation.
|
|
||||||
|
|
||||||
:param item: The service item to be amended
|
:param item: The service item to be amended
|
||||||
:param song: The song to be used to generate the footer
|
:param song: The song to be used to generate the footer
|
||||||
|
:return List of all authors (only required for initial song generation)
|
||||||
"""
|
"""
|
||||||
author_list = [str(author.display_name) for author in song.authors]
|
authors_words = []
|
||||||
|
authors_music = []
|
||||||
|
authors_translation = []
|
||||||
|
authors_none = []
|
||||||
|
for author_song in song.authors_songs:
|
||||||
|
if author_song.author_type == Author.TYPE_WORDS:
|
||||||
|
authors_words.append(author_song.author.display_name)
|
||||||
|
elif author_song.author_type == Author.TYPE_MUSIC:
|
||||||
|
authors_music.append(author_song.author.display_name)
|
||||||
|
elif author_song.author_type == Author.TYPE_TRANSLATION:
|
||||||
|
authors_translation.append(author_song.author.display_name)
|
||||||
|
else:
|
||||||
|
authors_none.append(author_song.author.display_name)
|
||||||
|
authors_all = authors_words + authors_music + authors_translation + authors_none
|
||||||
item.audit = [
|
item.audit = [
|
||||||
song.title, author_list, song.copyright, str(song.ccli_number)
|
song.title, authors_all, song.copyright, str(song.ccli_number)
|
||||||
]
|
]
|
||||||
item.raw_footer = []
|
item.raw_footer = []
|
||||||
item.raw_footer.append(song.title)
|
item.raw_footer.append(song.title)
|
||||||
item.raw_footer.append(create_separated_list(author_list))
|
if authors_none:
|
||||||
|
item.raw_footer.append("%s: %s"%(translate('OpenLP.Ui', 'Written by'), create_separated_list(authors_words)))
|
||||||
|
if authors_words:
|
||||||
|
item.raw_footer.append("%s: %s"%(Author.Types[Author.TYPE_WORDS], create_separated_list(authors_words)))
|
||||||
|
if authors_music:
|
||||||
|
item.raw_footer.append("%s: %s"%(Author.Types[Author.TYPE_MUSIC], create_separated_list(authors_music)))
|
||||||
|
if authors_translation:
|
||||||
|
item.raw_footer.append("%s: %s"%(Author.Types[Author.TYPE_TRANSLATION], create_separated_list(authors_translation)))
|
||||||
|
if not authors_all: #No authors defined
|
||||||
|
item.raw_footer.append(SongStrings.AuthorUnknown)
|
||||||
item.raw_footer.append(song.copyright)
|
item.raw_footer.append(song.copyright)
|
||||||
if Settings().value('core/ccli number'):
|
if Settings().value('core/ccli number'):
|
||||||
item.raw_footer.append(translate('SongsPlugin.MediaItem',
|
item.raw_footer.append(translate('SongsPlugin.MediaItem',
|
||||||
'CCLI License: ') + Settings().value('core/ccli number'))
|
'CCLI License: ') + Settings().value('core/ccli number'))
|
||||||
return author_list
|
return authors_all
|
||||||
|
|
||||||
def service_load(self, item):
|
def service_load(self, item):
|
||||||
"""
|
"""
|
||||||
|
@ -36,7 +36,7 @@ from sqlalchemy.sql.expression import func, false, null, text
|
|||||||
|
|
||||||
from openlp.core.lib.db import get_upgrade_op
|
from openlp.core.lib.db import get_upgrade_op
|
||||||
|
|
||||||
__version__ = 3
|
__version__ = 4
|
||||||
|
|
||||||
|
|
||||||
def upgrade_1(session, metadata):
|
def upgrade_1(session, metadata):
|
||||||
@ -82,3 +82,12 @@ def upgrade_3(session, metadata):
|
|||||||
op.add_column('songs', Column('temporary', types.Boolean(create_constraint=False), server_default=false()))
|
op.add_column('songs', Column('temporary', types.Boolean(create_constraint=False), server_default=false()))
|
||||||
else:
|
else:
|
||||||
op.add_column('songs', Column('temporary', types.Boolean(), server_default=false()))
|
op.add_column('songs', Column('temporary', types.Boolean(), server_default=false()))
|
||||||
|
|
||||||
|
def upgrade_4(session, metadata):
|
||||||
|
"""
|
||||||
|
Version 4 upgrade.
|
||||||
|
|
||||||
|
This upgrade adds a column for author type to the authors table
|
||||||
|
"""
|
||||||
|
op = get_upgrade_op(session)
|
||||||
|
op.add_column('authors_songs', Column('author_type', types.String(), primary_key=True, server_default=null()))
|
||||||
|
Loading…
Reference in New Issue
Block a user