Add support for the Author type property

This commit is contained in:
Samuel Mehrbrodt 2014-03-28 17:06:16 +01:00
parent fbf1183419
commit 71b34578d7
7 changed files with 100 additions and 35 deletions

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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):
""" """

View File

@ -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()))