← Back to team overview

openlp-core team mailing list archive

Re: [Merge] lp:~supagu/openlp/planningcenter-branch into lp:openlp

 

Review: Needs Fixing

Right, now for the review.

This is not going to go into OpenLP 2.2, as we have already hit feature-freeze, but this is a great candidate for OpenLP 2.4. So please note, we're not going to merge it until trunk is open for 2.4 development.

We changed our coding standards slightly after OpenLP 2.0.x came out. I'm sorry, we didn't update the wiki, so if you read the document on the wiki (which it looks like you did), then there are a few parts which are going to need to change. You'll be pleased to hear that I've since updated the wiki.

All functions, methods and variables should now use words_separated_by_underscores. We no longer use camelCase in any of our code, not even the Qt/Python hybrid classes. Not all our existing code has been converted yet, but this is the way we're going. Please can you update your code to follow this.

Please remove youtube-dl and the rest of the code associated with it. It is against YouTube's terms and conditions, and as a Christian project we will not participate in actions that contravene conditions laid out by services like YouTube.

We use single quotes for strings, not double quotes.

None of your strings are translatable. OpenLP is used in a number of different countries across the world, and they depend on being able to translate OpenLP into their own language.

What is the monkeypatch for? Why do we need to monkeypatch (HACK ALERT!) instead of extending the existing code?

You have no tests. No code without tests will be accepted. Also, since this is a huge change, I won't accept it without at least 50% code coverage.

Diff comments:

> === added directory 'openlp/plugins/planningcenter'
> === added file 'openlp/plugins/planningcenter/__init__.py'
> --- openlp/plugins/planningcenter/__init__.py	1970-01-01 00:00:00 +0000
> +++ openlp/plugins/planningcenter/__init__.py	2015-09-23 12:21:33 +0000
> @@ -0,0 +1,24 @@
> +# -*- coding: utf-8 -*-
> +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
> +
> +###############################################################################
> +# OpenLP - Open Source Lyrics Projection                                      #
> +# --------------------------------------------------------------------------- #
> +# Copyright (c) 2008-2015 OpenLP Developers                                   #
> +# --------------------------------------------------------------------------- #
> +# This program is free software; you can redistribute it and/or modify it     #
> +# under the terms of the GNU General Public License as published by the Free  #
> +# Software Foundation; version 2 of the License.                              #
> +#                                                                             #
> +# This program is distributed in the hope that it will be useful, but WITHOUT #
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
> +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
> +# more details.                                                               #
> +#                                                                             #
> +# You should have received a copy of the GNU General Public License along     #
> +# with this program; if not, write to the Free Software Foundation, Inc., 59  #
> +# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
> +###############################################################################
> +"""
> +The :mod:`alerts` module provides the Alerts plugin for producing impromptu on-screen announcements during a service.

This is not the alerts module anymore.

> +"""
> 
> === added file 'openlp/plugins/planningcenter/forms/logindialog.py'
> --- openlp/plugins/planningcenter/forms/logindialog.py	1970-01-01 00:00:00 +0000
> +++ openlp/plugins/planningcenter/forms/logindialog.py	2015-09-23 12:21:33 +0000
> @@ -0,0 +1,84 @@
> +# -*- coding: utf-8 -*-
> +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
> +
> +###############################################################################
> +# OpenLP - Open Source Lyrics Projection                                      #
> +# --------------------------------------------------------------------------- #
> +# Copyright (c) 2008-2015 OpenLP Developers                                   #
> +# --------------------------------------------------------------------------- #
> +# This program is free software; you can redistribute it and/or modify it     #
> +# under the terms of the GNU General Public License as published by the Free  #
> +# Software Foundation; version 2 of the License.                              #
> +#                                                                             #
> +# This program is distributed in the hope that it will be useful, but WITHOUT #
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
> +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
> +# more details.                                                               #
> +#                                                                             #
> +# You should have received a copy of the GNU General Public License along     #
> +# with this program; if not, write to the Free Software Foundation, Inc., 59  #
> +# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
> +###############################################################################
> +
> +import os
> +
> +from PyQt4 import QtGui, QtCore, uic
> +
> +from openlp.core.common import Settings
> +from openlp.core.common import AppLocation, translate
> +from openlp.plugins.planningcenter.lib import Session
> +
> +class LoginDialog(QtGui.QDialog):
> +    """
> +    Provide UI for displaying plans and schedules
> +    """
> +    def __init__(self, parent):
> +        """
> +        Initialise the alert form
> +        """
> +        extra_settings = {
> +            'planningcenter/login email': ""
> +        }
> +        Settings.extend_default_settings(extra_settings)
> +        QtGui.QDialog.__init__(self, parent)
> +        self.setupUi()
> +
> +    def setupUi(self):
> +        """
> +        Setup the UI
> +        """
> +        uiPath = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
> +                                     'planningcenter', 'resources', 'logindialog.ui')
> +        uic.loadUi(uiPath, self)

Please convert your forms to Python. We don't use UIC or .ui files.

> +        self.setWindowIcon(self.parent().windowIcon())
> +
> +        settings = Settings()
> +        settings.beginGroup('planningcenter')
> +        self.emailEdit.setText(settings.value('login email'))
> +        settings.endGroup()
> +
> +        # if there is a an email address, then give focus to password field
> +        if len(self.emailEdit.text()) > 0:
> +            self.passwordEdit.setFocus();
> +
> +
> +    def exec_(self):
> +        """
> +        Execute the dialog and return the exit code.
> +        """
> +        return QtGui.QDialog.exec_(self)
> +
> +    def accept(self):
> +        s = Session()
> +        s.login(self.emailEdit.text(), self.passwordEdit.text()) 

No single-letter variables.

> +        if s.isLoggedIn():
> +            settings = Settings()
> +            settings.beginGroup('planningcenter')
> +            settings.setValue('login email', self.emailEdit.text())
> +            settings.endGroup()
> +            QtGui.QDialog.accept(self)
> +        else:
> +            QtGui.QMessageBox.information(self,
> +                                          translate('PlanningCenterPlugin.LoginDialog', 'Login Failed'),
> +                                          translate('PlanningCenterPlugin.LoginDialog',
> +                                                    'Login failed, please ensure you have entered the correct email and password.'))
> 
> === added file 'openlp/plugins/planningcenter/forms/maindialog.py'
> --- openlp/plugins/planningcenter/forms/maindialog.py	1970-01-01 00:00:00 +0000
> +++ openlp/plugins/planningcenter/forms/maindialog.py	2015-09-23 12:21:33 +0000
> @@ -0,0 +1,121 @@
> +# -*- coding: utf-8 -*-
> +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
> +
> +###############################################################################
> +# OpenLP - Open Source Lyrics Projection                                      #
> +# --------------------------------------------------------------------------- #
> +# Copyright (c) 2008-2015 OpenLP Developers                                   #
> +# --------------------------------------------------------------------------- #
> +# This program is free software; you can redistribute it and/or modify it     #
> +# under the terms of the GNU General Public License as published by the Free  #
> +# Software Foundation; version 2 of the License.                              #
> +#                                                                             #
> +# This program is distributed in the hope that it will be useful, but WITHOUT #
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
> +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
> +# more details.                                                               #
> +#                                                                             #
> +# You should have received a copy of the GNU General Public License along     #
> +# with this program; if not, write to the Free Software Foundation, Inc., 59  #
> +# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
> +###############################################################################
> +
> +import os
> +import re
> +import logging
> +
> +from PyQt4 import QtGui, QtCore, uic
> +
> +from openlp.core.common import Registry, AppLocation, translate
> +from openlp.plugins.planningcenter.forms import LoginDialog, PlanImportDialog
> +from openlp.plugins.planningcenter.lib import Session
> +
> +class MainDialog(QtGui.QDialog):
> +    """
> +    Provide UI for displaying plans and schedules
> +    """
> +    def __init__(self, plugin):
> +        """
> +        Initialise the alert form
> +        """
> +        QtGui.QDialog.__init__(self, plugin.main_window)
> +        self.manager = plugin.manager
> +        self.plugin = plugin
> +        self.item_id = None
> +        self.setupUi()
> +
> +    def setupUi(self):
> +        """
> +        Setup the UI
> +        """
> +        uiPath = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
> +                                     'planningcenter', 'resources', 'maindialog.ui')
> +        uic.loadUi(uiPath, self)

Please convert your forms to Python. We don't use UIC or .ui files.

> +        self.setWindowIcon(QtGui.QIcon(self.plugin.iconPath()))
> +
> +    def exec_(self):
> +        """
> +        Execute the dialog and return the exit code.
> +        """
> +        self.treeWidget.clear()
> +        QtCore.QTimer.singleShot(100, self.updatePlans)
> +        return QtGui.QDialog.exec_(self)
> +
> +    def login(self):
> +        """
> +        Display the login dialog to let the user login
> +        """
> +        dlg = LoginDialog(self)
> +        dlg.exec_()
> +
> +    def updatePlans(self):
> +        """
> +        Grab the plans from planning center website and populate the tree widget
> +        """
> +        s = Session()

No single-letter variable names.

> +        if not s.isLoggedIn():
> +            self.login()
> +
> +        # user cancelled, so abort
> +        if not s.isLoggedIn():
> +            self.close()
> +            return
> +
> +        # populate the dashboard widget
> +        organisation = s.organisation()
> +
> +        for serviceType in organisation["service_types"]:
> +            serviceTypePlans = s.serviceTypePlans(serviceType["id"])
> +
> +            serviceTypeItem = QtGui.QTreeWidgetItem()
> +            serviceTypeItem.setText(0, serviceType["name"])
> +
> +            icon = QtGui.QIcon(self.planIconPath())
> +            serviceTypeItem.setIcon(0, icon)
> +
> +            self.treeWidget.invisibleRootItem().addChild(serviceTypeItem)
> +
> +            for plan in serviceTypePlans:
> +                planItem = QtGui.QTreeWidgetItem()
> +                planItem.setText(0, plan["dates"])
> +                planItem.setText(1, str(plan["id"]))
> +                serviceTypeItem.addChild(planItem)
> +
> +        self.treeWidget.expandAll()
> +
> +
> +    def on_treeWidget_itemDoubleClicked(self, item, column):
> +        """
> +        Time to import the plan
> +        """
> +        dlg = PlanImportDialog(self, item.text(1))
> +        dlg.exec_()
> +
> +    def planIconPath(self):
> +        """
> +        Get URL for plugin icon
> +        """
> +        return os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
> +                                     'planningcenter', 'resources', 'planicon.png')
> +
> +
> 
> === added file 'openlp/plugins/planningcenter/forms/planimportdialog.py'
> --- openlp/plugins/planningcenter/forms/planimportdialog.py	1970-01-01 00:00:00 +0000
> +++ openlp/plugins/planningcenter/forms/planimportdialog.py	2015-09-23 12:21:33 +0000
> @@ -0,0 +1,685 @@
> +# -*- coding: utf-8 -*-
> +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
> +
> +###############################################################################
> +# OpenLP - Open Source Lyrics Projection                                      #
> +# --------------------------------------------------------------------------- #
> +# Copyright (c) 2008-2015 OpenLP Developers                                   #
> +# --------------------------------------------------------------------------- #
> +# This program is free software; you can redistribute it and/or modify it     #
> +# under the terms of the GNU General Public License as published by the Free  #
> +# Software Foundation; version 2 of the License.                              #
> +#                                                                             #
> +# This program is distributed in the hope that it will be useful, but WITHOUT #
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
> +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
> +# more details.                                                               #
> +#                                                                             #
> +# You should have received a copy of the GNU General Public License along     #
> +# with this program; if not, write to the Free Software Foundation, Inc., 59  #
> +# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
> +###############################################################################
> +
> +import os
> +import types
> +import re
> +
> +from PyQt4 import QtGui, QtCore, uic
> +
> +from openlp.core.common import Settings
> +from openlp.core.common import AppLocation, translate
> +from openlp.plugins.planningcenter.lib import Session
> +from openlp.plugins.planningcenter.lib import Youtube
> +from openlp.core.common import Registry, RegistryProperties
> +from openlp.plugins.songs.lib.db import Author
> +from openlp.plugins.custom.lib import CustomXMLBuilder, CustomXMLParser
> +from openlp.plugins.custom.lib.db import CustomSlide
> +
> +class PlanImportDialog(QtGui.QDialog, RegistryProperties):
> +    """
> +    Provide UI for displaying plans and schedules
> +    """
> +    def __init__(self, parent, planId):
> +        """
> +        Initialise the alert form
> +        """
> +        QtGui.QDialog.__init__(self, parent)
> +        self.planId = planId
> +        self.setupUi()
> +        
> +    def setupUi(self):
> +        """
> +        Setup the UI
> +        """
> +        uiPath = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
> +                                     'planningcenter', 'resources', 'planimportdialog.ui')
> +        uic.loadUi(uiPath, self)

Please convert your forms to Python. We don't use UIC or .ui files.

> +        self.setWindowIcon(self.parent().windowIcon())
> +        self.progressBar.setValue(0)
> +        self.progressLabel.setText("Processing plan items...")
> +        self.secondLabel.setText("")
> +        self.textEdit.setText("")
> +        self.openDirButton.setVisible(False)
> +
> +    def exec_(self):
> +        """
> +        Execute the dialog and return the exit code.
> +        """
> +        QtCore.QTimer.singleShot(100, self.importPlan)
> +        return QtGui.QDialog.exec_(self)
> +
> +    def importPlan(self):
> +        """
> +        Download the media and import it 
> +        """
> +        # item count sucks, really should go add the media size of all items
> +        # or add a loading bar per item
> +        s = Session()

No single-letter variable names.

> +        plan = s.plan(self.planId)
> +
> +        self.progressCanceled = False
> +        self.progressComplete = False
> +
> +        # setup the absolute output directory from settings
> +        settings = Settings()
> +        settings.beginGroup('planningcenter')
> +        self.outputDirectory = settings.value('media directory')
> +        if self.outputDirectory != None and len(self.outputDirectory) > 0:
> +            self.outputDirectory = os.path.abspath(self.outputDirectory)
> +        else:
> +            self.outputDirectory = os.path.abspath(plan["dates"])
> +
> +        settings.endGroup()
> +
> +
> +        

Maximum of 1 open line, if absolutely necessary. If you can't read your code, your code needs to be refactored. Blank lines are an indication of unreadable code.

> +        self.textEdit.setText("Creating output directory: " + self.outputDirectory + "\n")
> +
> +        # create the directory
> +        if not os.path.exists(self.outputDirectory):
> +            os.makedirs(self.outputDirectory)
> +
> +        self.downloadAllItems(plan)
> +
> +        # clean up downloaded files?
> +        if self.progressCanceled:
> +            self.textEdit.append("\nCanceled by user")
> +            self.textEdit.setText("Canceled by user")
> +            self.close()
> +
> +        # save out the service
> +        self.service_manager._save_lite = False
> +        self.service_manager._file_name = os.path.join(self.outputDirectory, plan["dates"] + ".osz")
> +        self.service_manager.decide_save_method()
> +
> +        self.textEdit.append("\nComplete")
> +        self.progressLabel.setText("Complete")
> +
> +        # this gives the chance for the user to check any log output?
> +        self.button.setText("Ok")
> +        self.progressComplete = True
> +
> +        self.openDirButton.setVisible(True)
> +
> +    def downloadAllItems(self, plan):
> +        s = Session()

No single-letter variables.

> +        
> +        processedItems = []
> +
> +        # iterate over each item and gather media and songs for download
> +        i = 0

No single-letter variables.

> +        itemsCount = len(plan["items"])
> +        for item in plan["items"]:
> +            i = i + 1
> +            self.secondLabel.setText("Processing item " + str(i) + " of " + str(itemsCount))

Use string interpolation, or string formatting. 'Processing item %s of %s' % (counter, item_count)

> +
> +            percent = (i / float(itemsCount)) * 100
> +            self.progressBar.setValue(percent)
> +
> +            if item["type"] == "PlanMedia":
> +                itemMedia = item["plan_item_medias"]
> +                # no media
> +                if len(itemMedia) <= 0:
> +                    continue
> +
> +                media = s.media(itemMedia[0]["media_id"])
> +
> +                # Youtube url's are "public_url"
> +                mediaPublicUrl = None
> +                mediaUrl = None
> +                if "public_url" in media["attachments"][0]:
> +                    mediaPublicUrl = media["attachments"][0]["public_url"]
> +                else:
> +                    mediaUrl = media["attachments"][0]["url"]
> +
> +                fileName = media["attachments"][0]["filename"]
> +
> +                processedItems.append({"type": "PlanMedia", "mediaUrl": mediaUrl, "mediaPublicUrl": mediaPublicUrl, "fileName": fileName})
> +
> +            elif item["type"] == "PlanSong":
> +                title = item["title"]
> +                author = item["song"]["author"]
> +                arrangementId = item["arrangement"]["id"]
> +                arrangement = s.arrangement(arrangementId)
> +                lyrics = arrangement["chord_chart"]
> +                sequence = arrangement["sequence_to_s"]
> +                processedItems.append({"type": "PlanSong", "title": title, "author": author, "lyrics": lyrics, "sequence": sequence})
> +
> +            elif item["type"] == "PlanItem" and item["using_custom_slides"] == True:
> +                title = item["title"] + " - " + plan["dates"] # add the date as custom slide are custom... plan specific
> +                customSlides = item["custom_slides"]
> +                processedItems.append({"type": "PlanCustomSlides", "title": title, "customSlides": customSlides})
> +
> +
> +        i = 0

No single-letter variable names.

> +        itemsCount = len(processedItems)
> +        for item in processedItems:
> +            i = i + 1
> +            self.secondLabel.setText("Processing item " + str(i) + " of " + str(itemsCount))

Use string interpolation or string formatting.

> +
> +            if self.progressCanceled:
> +                return
> +
> +            if item["type"] == "PlanMedia":  
> +                self.progressLabel.setText("Downloading Media " + item["fileName"])

Use string interpolation or string formatting.

> +
> +                # when downloading from youtube, we dont know the extension till its downloaded
> +                newFileName = self.downloadMedia(item["mediaUrl"], item["mediaPublicUrl"], item["fileName"])
> +                if self.progressCanceled:
> +                    return
> +
> +                okMsg = ""
> +                if newFileName == None: 
> +                    okMsg = " [FAIL]"
> +
> +                # add media to openLP
> +                item["fileName"] = newFileName
> +                #fullFileName = os.path.join(self.outputDirectory, item["fileName"])
> +                self.addMediaToAppropriateManager(newFileName)
> +
> +                self.textEdit.append("Media Downloaded: " + item["fileName"] + okMsg)

Use string interpolation or string formatting.

> +
> +            elif item["type"] == "PlanSong":
> +                self.progressLabel.setText("Processing Song " + item["title"])

Use string interpolation or string formatting.

> +
> +                ok = self.processSong(item["title"], item["author"], item["lyrics"], item["sequence"])
> +
> +                okMsg = ""
> +                if ok != True: 
> +                    okMsg = " [FAIL]"
> +
> +                self.textEdit.append("Song Processed: " + item["title"] + okMsg)

Use string interpolation or string formatting. OK, I'm done. Not going to mention it again. You know what to do.

> +
> +            elif item["type"] == "PlanCustomSlides":
> +                self.progressLabel.setText("Processing Custom Slides " + item["title"])
> +                ok = self.processCustomSlides(item["title"], item["customSlides"])
> +
> +                okMsg = ""
> +                if ok != True: 
> +                    okMsg = " [FAIL]"
> +
> +                self.textEdit.append("Custom Slides Processed: " + item["title"] + okMsg)
> +
> +        return
> +
> +    def processCustomSlides(self, title, customSlides):
> +        """
> +        add the custom slides... code copied from custom/forms/editcustomform.py
> +        """
> +
> +        # see if slides already exist, if so, delete them to avoid duplicates
> +        item = self.custom.findItemByDisplayName(title)
> +        if item != None:
> +            self.custom.plugin.db_manager.delete_object(CustomSlide, item.data(QtCore.Qt.UserRole))
> +            self.custom.on_search_text_button_clicked()
> +
> +        custom_slide = CustomSlide()
> +        sxml = CustomXMLBuilder()
> +        for count in range(len(customSlides)):
> +            sxml.add_verse_to_lyrics('custom', str(count + 1), customSlides[count]['body'])
> +        custom_slide.title = title
> +        custom_slide.text = str(sxml.extract_xml(), 'utf-8')
> +        custom_slide.credits = ''
> +        custom_slide.theme_name = ''
> +        success = self.custom.plugin.db_manager.save_object(custom_slide)
> +        self.custom.auto_select_id = custom_slide.id
> +
> +        self.custom.on_clear_text_button_click()
> +        self.custom.on_selection_change()
> +
> +        # add the slides to the service
> +        item = self.custom.addMedia(custom_slide.id)
> +        if item == None:
> +            self.textEdit.append("Failed to add " + title)
> +            return False
> +
> +        self.custom.add_to_service(item)
> +
> +        return success
> +
> +    # This searches the song for the lyrics
> +    # for example Chorus or C is the sequence title, we return the text between Chorus and the next Verse for example
> +    def findLyrics(self, sequenceTitle, lyrics):

Use a docstring, not a comment, that's what they're made for.

> +        sequenceSplitExp = re.compile('(\D*)(\d*)')
> +        titleExp = re.compile('\(?(Verse|Chorus|Tag|Outro|Bridge|Misc)\)?\s?(\d?)|\(?(\S)(\d+)\)?')
> +
> +        sequenceTitleMatch = sequenceSplitExp.match(sequenceTitle)
> +        if sequenceTitleMatch == None:
> +            return ""
> +
> +        title = sequenceTitleMatch.group(1)
> +        number = sequenceTitleMatch.group(2)
> +        if number == "":
> +            number = "1"
> +
> +        
> +        endCharacter = len(lyrics)
> +        startCharacter = endCharacter
> +
> +        titleFound = False
> +        for match in titleExp.finditer(lyrics):
> +
> +            groupCount = len(match.groups())
> +            titleLong = match.group(1) if (groupCount >= 1) else "Undefined"
> +            titleShort = match.group(3) if (groupCount >= 3) else "Undefined"
> +            if titleLong == None:
> +                titleLong = "Undefined"
> +
> +            numberLong = match.group(2) if (groupCount >= 2) else ""
> +            numberShort = match.group(4) if (groupCount >= 4) else ""
> +
> +            if numberLong == "" and numberShort == None:
> +                numberLong = "1";
> +
> +            # ok, we found the next title in the song
> +            if titleFound:
> +                endCharacter = match.start()
> +                break
> +
> +            # we found a match to the title, so now we need to just set titleFound
> +            # and find the next match
> +            if (title == titleLong or title == titleShort or titleLong.startswith(title)) and (number == numberLong or number == numberShort):
> +                titleFound = True
> +                startCharacter = match.end()
> +
> +        # now we know the lyrics to extract from the song are between
> +        # startCharacter and endCharacter
> +        extractedLyrics = lyrics[startCharacter:endCharacter]
> +        extractedLyrics = extractedLyrics.strip('\n')
> +        return extractedLyrics
> +        
> +
> +    def processSong(self, title, authors, lyrics, sequence):
> +        """
> +        add the song
> +        """
> +        # emulate the user entering a new song
> +        self.songs.edit_song_form.new_song()
> +        self.songs.edit_song_form.title_edit.setText(title)
> +
> +        # emulate on_author_add_button_clicked
> +        authorList = authors.split(",")
> +        authorDisplayName = "Unknown Author"
> +        firstName = ""
> +        lastName = ""
> +        if len(authorList) >= 1:
> +            authorDisplayName = authorList[0].strip()
> +            authorNameList = authorDisplayName.split(" ")
> +            firstName = authorNameList[0] if len(authorNameList) > 1 else ""
> +            lastName = authorNameList[1] if len(authorNameList) > 1 else ""
> +            author = Author.populate(first_name=firstName, last_name=lastName, display_name=authorDisplayName)
> +
> +        # find existing song, if found simply use that
> +        displayName = title + " (" + authorDisplayName + ")";
> +        item = self.songs.findItemByDisplayName(displayName)
> +        if item:
> +            self.songs.add_to_service(item)
> +            return True
> +
> +        self.songs.edit_song_form.manager.save_object(author)
> +        self.songs.edit_song_form._add_author_to_list(author, "words")
> +        self.songs.edit_song_form.load_authors()
> +        self.songs.edit_song_form.authors_combo_box.setCurrentIndex(0)
> +        
> +        # setup each chrosu/verse into its own slide
> +        # TODO: break it based on number of lines
> +        sequenceList = sequence.split(", ")
> +        compiledSequenceList = []
> +        itemMap = {}
> +        for s in sequenceList:
> +            s = s.strip()
> +            if len(s) == 1:
> +                s += "1"
> +
> +            sLower = s.lower()
> +            sLower = sLower.replace("tag", "ending") # openlp doesnt recognise 'tag'
> +
> +            if sLower not in itemMap:
> +                extractedLyrics = self.findLyrics(s, lyrics)
> +                if extractedLyrics == None or len(extractedLyrics) <= 0:
> +                    continue
> +
> +                item = QtGui.QTableWidgetItem(extractedLyrics)
> +                item.setData(QtCore.Qt.UserRole, sLower)
> +                item.setText(extractedLyrics)
> +                itemMap[sLower] = item
> +                self.songs.edit_song_form.verse_list_widget.setRowCount(self.songs.edit_song_form.verse_list_widget.rowCount() + 1)
> +                self.songs.edit_song_form.verse_list_widget.setItem(self.songs.edit_song_form.verse_list_widget.rowCount() - 1, 0, item)
> +
> +            if sLower in itemMap:
> +                compiledSequenceList.append(sLower)
> +
> +        verseOrder = " ".join(compiledSequenceList)
> +        self.songs.edit_song_form.verse_order_edit.setText(verseOrder)
> +
> +        # emulate song form accept method
> +        self.songs.edit_song_form.clear_caches()
> +        if not self.songs.edit_song_form._validate_song():
> +            return False
> +
> +        self.songs.edit_song_form.save_song()
> +        songId = self.songs.edit_song_form.song.id
> +        self.songs.edit_song_form.song = None
> +
> +        # add new song to the list
> +        self.songs.on_clear_text_button_click()
> +
> +        # get the item and add to the service manager
> +        item = self.songs.findItem(songId)
> +        self.songs.add_to_service(item)
> +        return True
> +
> +    def addMediaToAppropriateManager(self, fileName):
> +        """
> +        given the file to the appropriate manager
> +        """
> +        self.addMedia(self.images, fileName)
> +        self.addMedia(self.presentations, fileName)
> +        self.addMedia(self.media, fileName)
> +
> +
> +    def addMedia(self, manager, fileName):
> +        # check file type is supported
> +        if not manager.handlesFile(fileName):
> +            return True
> +
> +        item = manager.addMedia(fileName)
> +        if item == None:
> +            self.textEdit.append("Failed to add " + fileName)
> +            return False
> +
> +        manager.add_to_service(item)
> +        return True
> +
> +          
> +    @property
> +    def songs(self):
> +        """
> +        Adds the songs plugin to the class dynamically
> +        """
> +        if not hasattr(self, '_songs') or not self._songs:
> +            self._songs = Registry().get('songs')
> +            monkeyPatchSongs(self._songs)
> +        return self._songs
> +          
> +    @property
> +    def presentations(self):
> +        """
> +        Adds the presentations plugin to the class dynamically
> +        """
> +        if not hasattr(self, '_presentations') or not self._presentations:
> +            self._presentations = Registry().get('presentations')
> +            monkeyPatchGeneric(self._presentations)
> +            monkeyPatchPresentations(self._presentations)
> +        return self._presentations
> +
> +    @property
> +    def custom(self):
> +        """
> +        Adds the custom slides plugin to the class dynamically
> +        """
> +        if not hasattr(self, '_custom') or not self._custom:
> +            self._custom = Registry().get('custom')
> +            monkeyPatchGeneric(self._custom)
> +            monkeyPatchCustom(self._custom)
> +        return self._custom
> +
> +    @property
> +    def media(self):
> +        """
> +        Adds the media plugin to the class dynamically
> +        """
> +        if not hasattr(self, '_media') or not self._media:
> +            self._media = Registry().get('media')
> +            monkeyPatchGeneric(self._media)
> +            monkeyPatchMedia(self._media)
> +        return self._media
> +
> +    @property
> +    def images(self):
> +        """
> +        Adds the images plugin to the class dynamically
> +        """
> +        if not hasattr(self, '_images') or not self._images:
> +            self._images = Registry().get('images')
> +            monkeyPatchGeneric(self._images)
> +            monkeyPatchImages(self._images)
> +        return self._images
> +
> +    def downloadMedia(self, url, publicUrl, fileName):
> +        """
> +        download a single media item
> +        """
> +
> +        # testing only! no way to guarantee file has not changed
> +        # this will just speed up development though
> +        # TODO: Can we do some date check?
> +        fullFileName = os.path.join(self.outputDirectory, fileName)
> +        if os.path.exists(fullFileName):
> +            return fullFileName
> +
> +        if publicUrl != None:
> +            return self.downloadPublicMedia(publicUrl, fullFileName)
> +
> +        return self.downloadPrivateMedia(url, fullFileName)
> +
> +    def downloadPublicMedia(self, url, fileName):
> +        """
> +        download a single media item from an external website
> +        """
> +        y = Youtube()
> +        return y.download(url, fileName)
> +
> +    def downloadPrivateMedia(self, url, fileName):
> +        """
> +        download a single media item from planning center website
> +        """
> +        s = Session()
> +        r = s.urlStream(url)
> +        if r.status_code != 200:
> +            return None
> +
> +        bytes = 0
> +        contentLength = r.headers['content-length']
> +        with open(fileName + ".part", 'wb') as f:
> +            for chunk in r.iter_content(chunk_size=10240):
> +                if chunk: # filter out keep-alive new chunks
> +                    bytes += len(chunk)
> +                    f.write(chunk)
> +                    f.flush()
> +
> +                    percent = (bytes / float(contentLength)) * 100
> +                    self.progressBar.setValue(percent)
> +
> +                if self.progressCanceled:
> +                    return None
> +
> +        os.rename(fileName + ".part", fileName)
> +        return fileName
> +
> +    def on_button_clicked(self):
> +        """
> +        close or abort
> +        """
> +        if self.progressComplete:
> +            self.close()
> +        else:
> +            self.progressCanceled = True
> +
> +    def on_openDirButton_clicked(self):
> +        """
> +        open explorer to allow us to copy to usb stick or what ever
> +        """
> +        QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.outputDirectory))
> +        
> +
> +
> +"""
> +Monkey patch for all managers
> +"""
> +def monkeyPatchGeneric(target):

What is the monkeypatch for? Why do we need to monkeypatch (HACK ALERT!) instead of extending the existing code?

> +    def addMedia(self, fileName):
> +        """
> +        Add the file, if it exists override it incase its changed and return the item handle for the added item
> +        """
> +        existingItem = self.findItem(fileName)
> +        if existingItem != None:
> +            return existingItem
> +
> +        self.validate_and_load([fileName])
> +        return self.findItem(fileName)
> +
> +    def handlesFile(target, fileName):
> +        """
> +        Using drag and drop support, determine if the manager can handle the given file type
> +        """
> +        file_type = fileName.split('.')[-1]
> +        if file_type.lower() not in target.on_new_file_masks:
> +            return False
> +
> +        return True
> +
> +    def findItem(self, fileName):
> +        """
> +        Find item given a filename - must be implemented
> +        """
> +        return None
> +
> +    target.addMedia = types.MethodType(addMedia, target)
> +    target.handlesFile = types.MethodType(handlesFile, target)
> +    target.findItem = types.MethodType(findItem, target)
> +
> +"""
> +Monkey patch for 'image' manager
> +"""
> +def monkeyPatchImages(target):
> +    def addMedia(self, fileName):
> +        """
> +        Add the file, if it exists override it incase its changed and return the item handle for the added item
> +        """
> +        existingItem = self.findItem(fileName)
> +        if existingItem != None:
> +            return existingItem
> +
> +        self.save_new_images_list([fileName])
> +        return self.findItem(fileName)
> +
> +    def findItem(self, fileName):
> +        """
> +        Find item given a filename
> +        """
> +        for count in range(self.list_view.count()):
> +            item = self.list_view.item(count)
> +            itemFileName = item.data(0, QtCore.Qt.UserRole).filename;
> +            if itemFileName == fileName:
> +                return item
> +
> +    target.addMedia = types.MethodType(addMedia, target)
> +    target.findItem = types.MethodType(findItem, target)
> +
> +"""
> +Monkey patch for 'presentations' manager
> +"""
> +def monkeyPatchPresentations(target):
> +    def findItem(self, fileName):
> +        """
> +        Find item given a filename
> +        """
> +        for count in range(self.list_view.count()):
> +            item = self.list_view.item(count)
> +            itemFileName = item.data(QtCore.Qt.UserRole)
> +            if itemFileName == fileName:
> +                return item
> +
> +    target.findItem = types.MethodType(findItem, target)
> +
> +"""
> +Monkey patch for 'custom' slides manager
> +"""
> +def monkeyPatchCustom(target):
> +    def findItem(self, fileName):
> +        """
> +        Find item given a id
> +        """
> +        for count in range(self.list_view.count()):
> +            item = self.list_view.item(count)
> +            itemFileName = item.data(QtCore.Qt.UserRole)
> +            if itemFileName == fileName:
> +                return item
> +
> +    def findItemByDisplayName(self, name):
> +        """
> +        Find item given a song name (author name)
> +        """
> +        for count in range(self.list_view.count()):
> +            item = self.list_view.item(count)
> +            itemName = item.data(QtCore.Qt.DisplayRole)
> +            if name == itemName:
> +                return item
> +
> +    target.findItem = types.MethodType(findItem, target)
> +    target.findItemByDisplayName = types.MethodType(findItemByDisplayName, target)
> +
> +"""
> +Monkey patch for 'media' manager
> +"""
> +def monkeyPatchMedia(target):
> +    def findItem(self, fileName):
> +        """
> +        Find item given a filename
> +        """
> +        for count in range(self.list_view.count()):
> +            item = self.list_view.item(count)
> +            itemFileName = item.data(QtCore.Qt.UserRole)
> +            if itemFileName == fileName:
> +                return item
> +
> +    target.findItem = types.MethodType(findItem, target)
> +
> +"""
> +Monkey patch for 'songs' manager
> +"""
> +def monkeyPatchSongs(target):
> +    def findItem(self, id):
> +        """
> +        Find item given a filename
> +        """
> +        for count in range(self.list_view.count()):
> +            item = self.list_view.item(count)
> +            itemName = item.data(QtCore.Qt.DisplayRole)
> +            itemId = item.data(QtCore.Qt.UserRole)
> +            if id == itemId:
> +                return item
> +
> +    def findItemByDisplayName(self, name):
> +        """
> +        Find item given a song name (author name)
> +        """
> +        for count in range(self.list_view.count()):
> +            item = self.list_view.item(count)
> +            itemName = item.data(QtCore.Qt.DisplayRole)
> +            if name == itemName:
> +                return item
> +
> +    target.findItem = types.MethodType(findItem, target)
> +    target.findItemByDisplayName = types.MethodType(findItemByDisplayName, target)
> \ No newline at end of file
> 
> === added file 'openlp/plugins/planningcenter/lib/db.py'
> --- openlp/plugins/planningcenter/lib/db.py	1970-01-01 00:00:00 +0000
> +++ openlp/plugins/planningcenter/lib/db.py	2015-09-23 12:21:33 +0000
> @@ -0,0 +1,55 @@
> +# -*- coding: utf-8 -*-
> +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
> +
> +###############################################################################
> +# OpenLP - Open Source Lyrics Projection                                      #
> +# --------------------------------------------------------------------------- #
> +# Copyright (c) 2008-2015 OpenLP Developers                                   #
> +# --------------------------------------------------------------------------- #
> +# This program is free software; you can redistribute it and/or modify it     #
> +# under the terms of the GNU General Public License as published by the Free  #
> +# Software Foundation; version 2 of the License.                              #
> +#                                                                             #
> +# This program is distributed in the hope that it will be useful, but WITHOUT #
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
> +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
> +# more details.                                                               #
> +#                                                                             #
> +# You should have received a copy of the GNU General Public License along     #
> +# with this program; if not, write to the Free Software Foundation, Inc., 59  #
> +# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
> +###############################################################################
> +"""
> +The :mod:`db` module provides the database and schema that is the backend for the Alerts plugin.
> +"""
> +
> +from sqlalchemy import Column, Table, types
> +from sqlalchemy.orm import mapper
> +
> +from openlp.core.lib.db import BaseModel, init_db
> +
> +
> +class AlertItem(BaseModel):

AlertItem in PlanningCenter plugin? Do you need this or do you just not know what it's about?

> +    """
> +    AlertItem model
> +    """
> +    pass
> +
> +
> +def init_schema(url):
> +    """
> +    Setup the alerts database connection and initialise the database schema
> +
> +    :param url:
> +        The database to setup
> +    """
> +    session, metadata = init_db(url)
> +
> +    alerts_table = Table('alerts', metadata,
> +                         Column('id', types.Integer(), primary_key=True),
> +                         Column('text', types.UnicodeText, nullable=False))
> +
> +    mapper(AlertItem, alerts_table)
> +
> +    metadata.create_all(checkfirst=True)
> +    return session


-- 
https://code.launchpad.net/~supagu/openlp/planningcenter-branch/+merge/272094
Your team OpenLP Core is subscribed to branch lp:openlp.


References