Esempio n. 1
0
    def __init__(self, router):
        rapidsms.app.App.__init__(self, router)
        
        # NB: this cannot be called globally
        # because of depencies between GNUTranslations (a -used here) 
        # and DJangoTranslations (b -used in views)
        # i.e. initializing b then a is ok, but a then b fails
        _init_translators()

        # command target. ToDo--get names from gettext...
        # needs to be here so that 'self' has meaning.
        # could also do the hasattr thing when calling instead
        self.cmd_targets = [ 
            # Pulaar
            (['naalde', 'naatde', 'tawtude'], {'lang':'pul','func':self.join}),
            ('yettoode', {'lang':'pul','func':self.register_name}),
            (['yaltude','iwde'], {'lang':'pul','func':self.leave}),
            (['dallal','ballal'], {'lang':'pul','func':self.help}),
            # Wolof
            (['boole', 'yokk', 'duggu'], {'lang':'wol','func':self.join}),
            (['genn', 'génn'], {'lang':'wol','func':self.leave}),
            (['sant', 'tur'], {'lang':'wol','func':self.register_name}),
            ('ndimbal', {'lang':'wol','func':self.help}),
            # Dyuola    
            (['unoken', 'ounoken'], {'lang':'dyu','func':self.join}),
            (['karees', 'karees'], {'lang':'dyu','func':self.register_name}),
            (['upur', 'oupour'], {'lang':'dyu','func':self.leave}),
            ('rambenom', {'lang':'dyu','func':self.help}),
            # French
            ('entrer', {'lang':'fr','func':self.join}),
            ('nom', {'lang':'fr','func':self.register_name}),
            ('quitter', {'lang':'fr','func':self.leave}),
            ('aide', {'lang':'fr','func':self.help}),
            (['créer', 'creer'], {'lang':'fr','func':self.create_village}),
            # English
            ('join', {'lang':'en','func':self.join}),
            ('name', {'lang':'en','func':self.register_name}),
            ('leave', {'lang':'en','func':self.leave}),
            ('language', {'lang':'en','func':self.lang}),
            ('help', {'lang':'en','func':self.help}),
            ('create', {'lang':'en','func':self.create_village}),
            ('member', {'lang':'en','func':self.member}),
            ('citizens', {'lang':'en','func':self.community_members}),
            ('remove', {'lang':'en','func':self.destroy_community}),
            ]
        
        self.cmd_matcher=BestMatch(self.cmd_targets)
        #villes=[(v.name, v) for v in Village.objects.all()]
        #self.village_matcher=BestMatch(villes, ignore_prefixes=['keur'])
        # swap dict so that we send in (name,code) tuples rather than (code,name
        self.lang_matcher=BestMatch([
                (names,code) for code,names in _G['SUPPORTED_LANGS'].items()
                ])
    def test08Spaces(self):
        class data():
            pass
        
        print "Add 'spaceyname  ' to check for correct treatment of spacey behaviour"
        matcher = BestMatch(targets=[('spaceyname  ', data())])
        print "Search for spaceyname"
        res = matcher.match('spaceyname  ')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')
        res = matcher.match('spaceyname')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')

        print "Add 'spaceyname  ' with aliases 'alias1 ', ' alias2', ' alias3 '"
        matcher = BestMatch(targets=[['spaceyname  ', 'alias1 ',' alias2', ' alias3 ']])
        print "Search for spaceyname"
        res = matcher.match('spaceyname  ')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')
        print "Search for alias3"
        res = matcher.match(' alias3 ')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')
        res = matcher.match('alias3')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')

        print "Add 'spaceyname  ' with aliases 'alias1 ', ' alias2', ' alias3 '"
        matcher = BestMatch(targets=[(['spaceyname  ', 'alias1 ',' alias2', ' alias3 '],data())])
        print "Search for spaceyname"
        res = matcher.match('spaceyname')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')
 def setUp(self):
     self.cityM = BestMatch(city_targets)
     self.cmdM = BestMatch(command_targets)
     self.restoM = BestMatch(resto_targets)
     self.simiM = BestMatch(similar_targets)
class TestBestMatch(unittest.TestCase):
    def setUp(self):
        self.cityM = BestMatch(city_targets)
        self.cmdM = BestMatch(command_targets)
        self.restoM = BestMatch(resto_targets)
        self.simiM = BestMatch(similar_targets)

    def test01BasicMatch(self):
        print
        print "BASIC MATCHING"
        print "Find 'join'"
        res = self.cmdM.match('join')
        self.assertTrue(len(res) == 1 and res[0] == 'join')

        print "Find 'j'"
        res = self.cmdM.match('j')
        self.assertTrue(len(res) == 1 and res[0] == 'join')

        print "Find None"
        res = self.cmdM.match(None)
        self.assertTrue(len(res) == 0)

        print "Find ''"
        res = self.cmdM.match('')
        self.assertTrue(len(res) == 0)

        print "Add and find 'help'"
        self.cmdM.add_target('help')
        res = self.cmdM.match('help')
        self.assertTrue(len(res) == 1 and res[0] == 'help')

        print "Still find 'join'"
        res = self.cmdM.match('j')
        self.assertTrue(len(res) == 1 and res[0] == 'join')

        print "Add 'later' and find 'later'"
        self.cmdM.add_target('later')
        res = self.cmdM.match('later')
        self.assertTrue(len(res) == 1 and res[0] == 'later')

        print "Find 'l' and get 'later' and 'leave'"
        res = self.cmdM.match('l')
        self.assertTrue(set(res) == set(['later', 'leave']))

    def test02ExactMatch(self):
        print
        print "EXACT MATCHING"
        print "Find 'bob'"
        res = self.simiM.match('bob')
        print res
        self.assertTrue(len(res) == 1 and res[0] == 'bob')

    def test03UnAnchored(self):
        print
        print "UNANCHORED SEARCH"
        res = self.cityM.match('field', anchored=False)
        print "Unanchored search for 'field': %s" % ','.join(res)
        self.assertTrue(set(res) == set(['springfield', 'westfield']))

    def test04PrefixMatch(self):
        print
        print "IGNORE PREFIX MATCHING"
        res = self.restoM.match('panisse', anchored=True)
        print "Unprefixed search for 'panisse': %s" % ','.join(res)
        self.assertTrue(len(res) == 0)

        print "Add prefix 'chez'"
        self.restoM.add_ignore_prefix('Chez')

        res = self.restoM.match('panisse', anchored=True)
        print "Prefixed search for 'panisse': %s" % ','.join(res)
        self.assertTrue(len(res) == 1 and res[0] == 'chez panisse')

        res = self.restoM.match('chez', anchored=True)
        print "Prefixed search for 'chez': %s" % ','.join(res)
        self.assertTrue(set(res) == set(resto_targets[:3]))

        print "Add 'hotel' to prefix list"
        self.restoM.add_ignore_prefix('hotel')

        res = self.restoM.match('lando', anchored=True)
        print "Prefixed search for 'lando': %s" % ','.join(res)
        self.assertTrue(set(res) == set(['chez lando', 'hotel lando']))

        print "Add 'burger' to prefix list"
        self.restoM.add_ignore_prefix('burger')
        res = self.restoM.match('burger', anchored=True)
        print "Prefixed search for 'burger': %s" % ','.join(res)
        self.assertTrue(set(res) == set(resto_targets[-3:]))

    def test05WithData(self):
        print
        print "TARGETS WITH DATA"
        self.cmdM.targets = zip(command_targets, command_data)

        print "Retrieve function for data"
        res = self.cmdM.match('j', with_data=True)
        self.assertTrue(res[0][1]() == 'join_data')

        print "Retrieve string"
        res = self.cmdM.match('le', with_data=True)
        self.assertTrue(res[0][1] == 'leave_data')

        print "Retrieve None"
        res = self.cmdM.match('n', with_data=True)
        self.assertTrue(res[0][1] is None)

        print "Retrieve dict"
        res = self.cmdM.match('cr', with_data=True)
        self.assertTrue(res[0][1]['key'] == 'value')

    def test06AddRemoveTargets(self):
        print
        print 'Add Buffalo'
        self.cityM.add_target('buffalo')
        res = self.cityM.match('buf')
        self.assertTrue(len(res) == 1 and res[0] == 'buffalo')

        res = self.cityM.match('new')
        self.assertTrue(set(res) == set(['newton', 'newberry', 'new orleans']))

        print 'Remove Buffalo'
        self.cityM.remove_target('buffalo')
        res = self.cityM.match('buF')
        self.assertTrue(len(res) == 0)

        print "Add prefix 'new'"
        self.cityM.add_ignore_prefix('new')
        res = self.cityM.match('ton')
        self.assertTrue(len(res) == 1 and res[0] == 'newton')

        print "Remove prefix 'new'"
        self.cityM.remove_ignore_prefix('new')
        res = self.cityM.match('ton')
        self.assertTrue(len(res) == 0)

        print "Add target with data. (buffalo, rocks)"
        self.cityM.add_target(('buffalo', 'rocks'))
        res = self.cityM.match('BuF', with_data=True)
        self.assertTrue(len(res) == 1 and res[0] == ('buffalo', 'rocks'))

    def test07Aliases(self):
        print
        print "Add 'boston' with aliases 'the hub', 'beantown'"
        self.cityM.add_target(['boston', 'the hub', 'beantown'])
        print "Search for Beantown"
        res = self.cityM.match('beanTown')
        print res
        self.assertTrue(len(res) == 1 and res[0] == 'boston')

        print "Search for Boston"
        res = self.cityM.match('boston')
        self.assertTrue(len(res) == 1 and res[0] == 'boston')

        print "Add 'redsox country' as alias"
        self.cityM.add_alias_for_target('boston', 'redsox country')
        res = self.cityM.match('redsox')
        self.assertTrue(len(res) == 1 and res[0] == 'boston')

        print "Remove 'beantown'"
        self.cityM.remove_alias_for_target('boston', 'beantown')
        res = self.cityM.match('beantown')
        self.assertTrue(len(res) == 0)

        print "Test get aliases"
        self.assertTrue(
            set(self.cityM.get_aliases_for_target('boston')) == set(
                ['redsox country', 'the hub']))
 def setUp(self):
     self.cityM=BestMatch(city_targets)
     self.cmdM=BestMatch(command_targets)
     self.restoM=BestMatch(resto_targets)    
     self.simiM=BestMatch(similar_targets)
class TestBestMatch(unittest.TestCase):
    
    def setUp(self):
        self.cityM=BestMatch(city_targets)
        self.cmdM=BestMatch(command_targets)
        self.restoM=BestMatch(resto_targets)    
        self.simiM=BestMatch(similar_targets)
        
    def test01BasicMatch(self):
        print
        print "BASIC MATCHING"
        print "Find 'join'"
        res=self.cmdM.match('join')
        self.assertTrue(len(res)==1 and res[0]=='join')

        print "Find 'j'"
        res=self.cmdM.match('j')
        self.assertTrue(len(res)==1 and res[0]=='join')

        print "Find None"
        res=self.cmdM.match(None)
        self.assertTrue(len(res)==0)

        print "Find ''"
        res=self.cmdM.match('')
        self.assertTrue(len(res)==0)

        print "Add and find 'help'"
        self.cmdM.add_target('help')
        res=self.cmdM.match('help')
        self.assertTrue(len(res)==1 and res[0]=='help')

        print "Still find 'join'"
        res=self.cmdM.match('j')
        self.assertTrue(len(res)==1 and res[0]=='join')

        print "Add 'later' and find 'later'"
        self.cmdM.add_target('later')
        res=self.cmdM.match('later')
        self.assertTrue(len(res)==1 and res[0]=='later')

        print "Find 'l' and get 'later' and 'leave'"
        res=self.cmdM.match('l')
        self.assertTrue(set(res)==set(['later','leave']))

    def test02ExactMatch(self):
        print
        print "EXACT MATCHING"
        print "Find 'bob'"
        res=self.simiM.match('bob')
        print res
        self.assertTrue(len(res)==1 and res[0]=='bob')

    def test03UnAnchored(self):
        print
        print "UNANCHORED SEARCH"
        res=self.cityM.match('field',anchored=False)
        print "Unanchored search for 'field': %s" % ','.join(res)
        self.assertTrue(set(res)==set(['springfield','westfield']))

    def test04PrefixMatch(self):
        print
        print "IGNORE PREFIX MATCHING"
        res=self.restoM.match('panisse',anchored=True)
        print "Unprefixed search for 'panisse': %s" % ','.join(res)
        self.assertTrue(len(res)==0)

        print "Add prefix 'chez'"
        self.restoM.add_ignore_prefix('Chez')

        res=self.restoM.match('panisse',anchored=True)
        print "Prefixed search for 'panisse': %s" % ','.join(res)
        self.assertTrue(len(res)==1 and res[0]=='chez panisse')

        res=self.restoM.match('chez',anchored=True)
        print "Prefixed search for 'chez': %s" % ','.join(res)
        self.assertTrue(set(res)==set(resto_targets[:3]))

        print "Add 'hotel' to prefix list"
        self.restoM.add_ignore_prefix('hotel')

        res=self.restoM.match('lando',anchored=True)
        print "Prefixed search for 'lando': %s" % ','.join(res)
        self.assertTrue(set(res)==set(['chez lando','hotel lando']))

        print "Add 'burger' to prefix list"
        self.restoM.add_ignore_prefix('burger')
        res=self.restoM.match('burger',anchored=True)
        print "Prefixed search for 'burger': %s" % ','.join(res)
        self.assertTrue(set(res)==set(resto_targets[-3:]))

    def test05WithData(self):
        print
        print "TARGETS WITH DATA"
        self.cmdM.targets=zip(command_targets,command_data)

        print "Retrieve function for data"
        res=self.cmdM.match('j',with_data=True)
        self.assertTrue(res[0][1]()=='join_data')

        print "Retrieve string"
        res=self.cmdM.match('le',with_data=True)
        self.assertTrue(res[0][1]=='leave_data')

        print "Retrieve None"
        res=self.cmdM.match('n',with_data=True)
        self.assertTrue(res[0][1] is None)

        print "Retrieve dict"
        res=self.cmdM.match('cr',with_data=True)
        self.assertTrue(res[0][1]['key']=='value')

    def test06AddRemoveTargets(self):
        print
        print 'Add Buffalo'
        self.cityM.add_target('buffalo')
        res = self.cityM.match('buf')
        self.assertTrue(len(res)==1 and res[0]=='buffalo')

        res = self.cityM.match('new')
        self.assertTrue(set(res)==set(['newton','newberry','new orleans']))

        print 'Remove Buffalo'
        self.cityM.remove_target('buffalo')
        res = self.cityM.match('buF')
        self.assertTrue(len(res)==0)

        print "Add prefix 'new'"
        self.cityM.add_ignore_prefix('new')
        res = self.cityM.match('ton')
        self.assertTrue(len(res)==1 and res[0]=='newton')

        print "Remove prefix 'new'"
        self.cityM.remove_ignore_prefix('new')
        res = self.cityM.match('ton')
        self.assertTrue(len(res)==0)

        print "Add target with data. (buffalo, rocks)"
        self.cityM.add_target(('buffalo','rocks'))
        res = self.cityM.match('BuF',with_data=True)
        self.assertTrue(len(res)==1 and res[0]==('buffalo','rocks'))

    def test07Aliases(self):
        print
        print "Add 'boston' with aliases 'the hub', 'beantown'"
        self.cityM.add_target(['boston', 'the hub', 'beantown'])
        print "Search for Beantown"
        res = self.cityM.match('beanTown')
        print res
        self.assertTrue(len(res)==1 and res[0]=='boston')
        
        print "Search for Boston"
        res = self.cityM.match('boston')
        self.assertTrue(len(res)==1 and res[0]=='boston')

        print "Add 'redsox country' as alias"
        self.cityM.add_alias_for_target('boston', 'redsox country')
        res = self.cityM.match('redsox')
        self.assertTrue(len(res)==1 and res[0]=='boston')

        print "Remove 'beantown'"
        self.cityM.remove_alias_for_target('boston', 'beantown')
        res = self.cityM.match('beantown')
        self.assertTrue(len(res)==0)

        print "Test get aliases"
        self.assertTrue(set(self.cityM.get_aliases_for_target('boston'))==
                        set(['redsox country', 'the hub']))
        
    def test08Spaces(self):
        class data():
            pass
        
        print "Add 'spaceyname  ' to check for correct treatment of spacey behaviour"
        matcher = BestMatch(targets=[('spaceyname  ', data())])
        print "Search for spaceyname"
        res = matcher.match('spaceyname  ')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')
        res = matcher.match('spaceyname')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')

        print "Add 'spaceyname  ' with aliases 'alias1 ', ' alias2', ' alias3 '"
        matcher = BestMatch(targets=[['spaceyname  ', 'alias1 ',' alias2', ' alias3 ']])
        print "Search for spaceyname"
        res = matcher.match('spaceyname  ')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')
        print "Search for alias3"
        res = matcher.match(' alias3 ')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')
        res = matcher.match('alias3')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')

        print "Add 'spaceyname  ' with aliases 'alias1 ', ' alias2', ' alias3 '"
        matcher = BestMatch(targets=[(['spaceyname  ', 'alias1 ',' alias2', ' alias3 '],data())])
        print "Search for spaceyname"
        res = matcher.match('spaceyname')
        print res
        print "solution is '%s'" % res[0]
        self.assertTrue(len(res)==1 and res[0]=='spaceyname')
Esempio n. 7
0
class App(rapidsms.app.App):
    def __init__(self, router):
        rapidsms.app.App.__init__(self, router)
        
        # NB: this cannot be called globally
        # because of depencies between GNUTranslations (a -used here) 
        # and DJangoTranslations (b -used in views)
        # i.e. initializing b then a is ok, but a then b fails
        _init_translators()

        # command target. ToDo--get names from gettext...
        # needs to be here so that 'self' has meaning.
        # could also do the hasattr thing when calling instead
        self.cmd_targets = [ 
            # NOTE: make sure all commands are unicode strings!
            # Pulaar
            ([u'naalde', u'naatde', u'tawtude',u'naattugol'], {'lang':'pul','func':self.join}),
            (u'yettoode', {'lang':'pul','func':self.register_name}),
            ([u'yaltude',u'iwde'], {'lang':'pul','func':self.leave}),
            ([u'dallal',u'ballal'], {'lang':'pul','func':self.help}),
            (u'penngugol', {'lang':'pul','func':self.create_village}),
            # Wolof
            ([u'boole', u'yokk', u'duggu'], {'lang':'wol','func':self.join}),
            ([u'genn', u'génn'], {'lang':'wol','func':self.leave}),
            ([u'sant', u'tur'], {'lang':'wol','func':self.register_name}),
            (u'ndimbal', {'lang':'wol','func':self.help}),
            # Dyuola    
            ([u'unoken', u'ounoken'], {'lang':'dyu','func':self.join}),
            ([u'karees', u'karees'], {'lang':'dyu','func':self.register_name}),
            ([u'upur', u'oupour'], {'lang':'dyu','func':self.leave}),
            (u'rambenom', {'lang':'dyu','func':self.help}),
            # Soninke
            (u'ro', {'lang':'snk','func':self.join}),
            (u'toxo', {'lang':'snk','func':self.register_name}),
            (u'bagu', {'lang':'snk','func':self.leave}),
            (u'deema', {'lang':'snk','func':self.help}),
            (u'taga', {'lang':'snk','func':self.create_village}),
            # Mandinka
            (u'koo', {'lang':'mnk','func':self.join}),
            (u'ntoo', {'lang':'mnk','func':self.register_name}),
            (u'nbetaamala', {'lang':'mnk','func':self.leave}),
            (u"n'deemaa", {'lang':'mnk','func':self.help}),
            # French
            (u'entrer', {'lang':'fr','func':self.join}),
            (u'nom', {'lang':'fr','func':self.register_name}),
            (u'quitter', {'lang':'fr','func':self.leave}),
            (u'aide', {'lang':'fr','func':self.help}),
            ([u'créer', u'creer'], {'lang':'fr','func':self.create_village}),
            (u'enlever', {'lang':'fr','func':self.destroy_community}),
            (u'langue', {'lang':'fr','func':self.lang}),
            # English
            (u'join', {'lang':'en','func':self.join}),
            (u'name', {'lang':'en','func':self.register_name}),
            (u'leave', {'lang':'en','func':self.leave}),
            (u'help', {'lang':'en','func':self.help}),
            (u'create', {'lang':'en','func':self.create_village}),
            (u'member', {'lang':'en','func':self.member}),
            (u'citizens', {'lang':'en','func':self.community_members}),
            (u'remove', {'lang':'en','func':self.destroy_community}),
            ]
        
        self.cmd_matcher=BestMatch(self.cmd_targets)
        #villes=[(v.name, v) for v in Village.objects.all()]
        #self.village_matcher=BestMatch(villes, ignore_prefixes=['keur'])
        # swap dict so that we send in (name,code) tuples rather than (code,name
        self.lang_matcher=BestMatch([
                (names,code) for code,names in _G['SUPPORTED_LANGS'].items()
                ])
        
    def __get_village_matcher(self):
        """
        HACK to force reload of names before each match
        
        """
        villes = []
        for v in Village.objects.all():
            names = [v.name] + [a.alias for a in v.aliases.all()]
            villes.append( (names,v) )
        return BestMatch(villes, ignore_prefixes=['keur'])
        
    def configure(self, **kwargs):
        try:
            _G['DEFAULT_LANG'] = kwargs.pop('default_lang')
        except:
            pass

        try:
            _G['ADMIN_CMD_PWD'] = kwargs.pop('admin_cmd_pwd')
        except:
            pass

    def start(self):
        self.__loadFixtures()
    
    #####################
    # Message Lifecycle #
    #####################
    def handle(self, msg):
        self.__log_incoming_message(msg, villages_for_contact(msg.sender))
        self.debug("In handle smsforums: %s" % msg.text)
        
        # check permissions
        if msg.sender.perm_ignore:
            self.debug('Ignoring sender: %s' % msg.sender.signature)
            return False

        if not msg.sender.can_send:
            self.debug('Sender: %s does no have receive perms' % msg.sender.signature)
            self.__reply(msg,'inbound-message_rejected')
        
        # Ok, we're all good, start processing
        msg.sender.sent_message_accepted(msg)
        
        #
        # Now we figure out if it's a direct message, a command, or a blast
        #
        # ok, this is a little weird, but stay with me.
        # commands start with '.' '*' or '#'--the cmd markers. e.g. '.join <something>'
        # addresses are of form cmd_marker address cmd_mark--e.g. '.jeff. hello'
        #
        address=None
        rest=None

        # check for direct message first
        m=DM_MESSAGE_MATCHER.match(msg.text)
        if m is not None:
            address=m.group(1).strip()
            rest=m.group(2)
            if rest is not None:
                rest=rest.strip()
            return self.blast_direct(msg,address,rest)
        
        # are we a command?
        m=CMD_MESSAGE_MATCHER.match(msg.text)
        if m is None:
            # we are a blast
            return self.blast(msg)

        # we must be a command
        cmd,rest=m.groups()
        if cmd is None:
            #user tried to send some sort of command (a message with .,#, or *, but nothing after)
            self.__reply(msg,"command-not-understood")
            return True
        else:
            cmd=cmd.strip()

        if rest is not None:
            rest=rest.strip()

        # Now match the possible command to ones we know
        cmd_match=self.cmd_matcher.match(cmd,with_data=True)

        if len(cmd_match)==0:
            # no command match
            self.__reply(msg,"command-not-understood")
            return True

        if len(cmd_match)>1:
            # too many matches!
            self.__reply(msg, 'command-not-understood %(sug_1)s %(sug_rest)s', \
                              { 'sug_1':', '.join([t[0] for t in cmd_match[:-1]]),
                                'sug_rest':cmd_match[-1:][0][0]})
            return True
        #
        # Ok! We got a real command
        #
        cmd,data=cmd_match[0]
        #arg=msg_text[msg_match.end():]

        # set the senders default language, if not sent
        if msg.sender.locale is None:
            msg.sender.locale=data['lang']
            msg.sender.save()
        return data['func'](msg,arg=rest)

    def outgoing(self, msg):
        # TODO
        # create a ForumMessage class
        # log messages with associated domain
        # report on dashboard
        pass
        

    ####################
    # Command Handlers #
    ####################
    def help(self, msg,arg=None):
        if arg is not None and len(arg)>0:
            # see if it is a language and send help 
            # for that lang
            langs=self.lang_matcher.match(arg,with_data=True)
            if len(langs)==1:
                self.__reply(msg, "help-with-commands_%s" % langs[0][1])
                return True
            else:
                # send the list of available langs by passing
                # to the 'lang' command handler
                return self.help(msg)
            
        self.__reply(msg, "help-with-commands")
        return True

    @passwordProtectedCmd
    def create_village(self, msg, arg=None):
        self.debug("SMSFORUM:CREATEVILLAGE")        
        if arg is None or len(arg)<1:
            self.__reply(msg, "create-village-fail_no-village-name")
            return True
        else:
            village = arg

        if len(Village.objects.filter(name=village)) != 0:
            self.__reply(msg, "create-village-fail_village-already-exists %(village)s", {'village':village})
            return True
        try:
            # TODO: add administrator authentication
            if len(village) > MAX_VILLAGE_NAME_LEN:
                self.__reply(msg, "create-village-fail_name-too-long %(village)s %(max_char)d", \
                             {'village':village, 'max_char':MAX_VILLAGE_NAME_LEN} )
                return True
            ville = Village(name=village)
            ville.save()
            # self.village_matcher.add_target((village,ville))
            self.__reply(msg, "create-village-success %(village)s", {'village':village} )
        except:
            self.debug( traceback.format_exc() )
            traceback.print_exc()
            self.__reply(msg, "internal-error")

        return True
             
    def member(self,msg,arg=None):
        try:
            villages=villages_for_contact(msg.sender)
            if len(villages)==0:
                self.__reply(msg, "member-fail_not-member-of-village")
            else:
                village_names = ', '.join([v.name for v in villages])
                txt = "member-success %(village_names)s"
                if len(villages)>5: 
                    villages = villages[0:5]
                    txt = "member-success_long-list %(village_names)s"
                self.__reply(msg, txt, {"village_names":village_names})
        except:
            traceback.print_exc()
            self.debug( traceback.format_exc() )
            rsp= _st(msg.sender,"internal-error")
            self.debug(rsp)
            self.__reply(msg,rsp)
        return True

    @passwordProtectedCmd
    def community_members(self,msg,arg=None):
        if arg is None or len(arg)==0:
            self.__reply(msg, "citizens-fail_no-village")
            return True

        villes=self.__get_village_matcher().match(arg,with_data=True)
        if len(villes)==0:
            self.__reply(msg, "village-not-known %(unknown)s", {'unknown':arg})
            return True

        for name,ville in villes:
            members=[c.get_signature(max_len=10) for c in \
                         ville.flatten(klass=Contact)]
            if len(members)>20: 
                members = members[0:20]
                txt = 'citizens-success_long-list %(village)s %(citizens)s'
            else:
                txt = 'citizens-success %(village)s %(citizens)s'
                
            self.__reply(msg, txt, {'village':name, 'citizens':','.join(members)})
        return True

    @passwordProtectedCmd
    def destroy_community(self,msg,arg=None):
        if arg is None or len(arg)==0:
            self.__reply(msg, "remove-fail_no-village")
            return True

        try:
            # EXACT MATCH ONLY!
            ville=Village.objects.get(name=arg)
            ville.delete()
            # self.village_matcher.remove_target(arg)
            self.__reply(msg, "remove-success %(village)s", {'village': arg})
            return True
        except Exception, e:
            rsp= _st(msg.sender,"village-not-known %(unknown)s") % {'unknown':arg} 
            self.debug(rsp)
            self.__reply(msg,rsp)
        return True
Esempio n. 8
0
class App(rapidsms.app.App):
    def __init__(self, router):
        rapidsms.app.App.__init__(self, router)
        
        # NB: this cannot be called globally
        # because of depencies between GNUTranslations (a -used here) 
        # and DJangoTranslations (b -used in views)
        # i.e. initializing b then a is ok, but a then b fails
        _init_translators()

        # command target. ToDo--get names from gettext...
        # needs to be here so that 'self' has meaning.
        # could also do the hasattr thing when calling instead
        self.cmd_targets = [ 
            # Pulaar
            (['naalde', 'naatde', 'tawtude'], {'lang':'pul','func':self.join}),
            ('yettoode', {'lang':'pul','func':self.register_name}),
            (['yaltude','iwde'], {'lang':'pul','func':self.leave}),
            (['dallal','ballal'], {'lang':'pul','func':self.help}),
            # Wolof
            (['boole', 'yokk', 'duggu'], {'lang':'wol','func':self.join}),
            (['genn', 'génn'], {'lang':'wol','func':self.leave}),
            (['sant', 'tur'], {'lang':'wol','func':self.register_name}),
            ('ndimbal', {'lang':'wol','func':self.help}),
            # Dyuola    
            (['unoken', 'ounoken'], {'lang':'dyu','func':self.join}),
            (['karees', 'karees'], {'lang':'dyu','func':self.register_name}),
            (['upur', 'oupour'], {'lang':'dyu','func':self.leave}),
            ('rambenom', {'lang':'dyu','func':self.help}),
            # French
            ('entrer', {'lang':'fr','func':self.join}),
            ('nom', {'lang':'fr','func':self.register_name}),
            ('quitter', {'lang':'fr','func':self.leave}),
            ('aide', {'lang':'fr','func':self.help}),
            (['créer', 'creer'], {'lang':'fr','func':self.create_village}),
            # English
            ('join', {'lang':'en','func':self.join}),
            ('name', {'lang':'en','func':self.register_name}),
            ('leave', {'lang':'en','func':self.leave}),
            ('language', {'lang':'en','func':self.lang}),
            ('help', {'lang':'en','func':self.help}),
            ('create', {'lang':'en','func':self.create_village}),
            ('member', {'lang':'en','func':self.member}),
            ('citizens', {'lang':'en','func':self.community_members}),
            ('remove', {'lang':'en','func':self.destroy_community}),
            ]
        
        self.cmd_matcher=BestMatch(self.cmd_targets)
        #villes=[(v.name, v) for v in Village.objects.all()]
        #self.village_matcher=BestMatch(villes, ignore_prefixes=['keur'])
        # swap dict so that we send in (name,code) tuples rather than (code,name
        self.lang_matcher=BestMatch([
                (names,code) for code,names in _G['SUPPORTED_LANGS'].items()
                ])
        
    def __get_village_matcher(self):
        """
        HACK to force reload of names before each match
        
        """
        villes=[(v.name, v) for v in Village.objects.all()]
        return BestMatch(villes, ignore_prefixes=['keur'])
        
    def configure(self, **kwargs):
        try:
            _G['DEFAULT_LANG'] = kwargs.pop('default_lang')
        except:
            pass

        try:
            _G['ADMIN_CMD_PWD'] = kwargs.pop('admin_cmd_pwd')
        except:
            pass

    def start(self):
        self.__loadFixtures()
    
    #####################
    # Message Lifecycle #
    #####################
    def handle(self, msg):
        self.__log_incoming_message(msg, villages_for_contact(msg.sender))
        self.debug("In handle smsforums: %s" % msg.text)
        
        # check permissions
        if msg.sender.perm_ignore:
            self.debug('Ignoring sender: %s' % msg.sender.signature)
            return False

        if not msg.sender.can_send:
            self.debug('Sender: %s does no have receive perms' % msg.sender.signature)
            self.__reply(msg,'inbound-message_rejected')
        
        # Ok, we're all good, start processing
        msg.sender.sent_message_accepted(msg)
        
        #
        # Now we figure out if it's a direct message, a command, or a blast
        #
        # ok, this is a little weird, but stay with me.
        # commands start with '.' '*' or '#'--the cmd markers. e.g. '.join <something>'
        # addresses are of form cmd_marker address cmd_mark--e.g. '.jeff. hello'
        #
        address=None
        rest=None

        # check for direct message first
        m=DM_MESSAGE_MATCHER.match(msg.text)
        if m is not None:
            address=m.group(1).strip()
            rest=m.group(2)
            if rest is not None:
                rest=rest.strip()
            return self.blast_direct(msg,address,rest)
        
        # are we a command?
        m=CMD_MESSAGE_MATCHER.match(msg.text)
        if m is None:
            # we are a blast
            return self.blast(msg)

        # we must be a command
        cmd,rest=m.groups()
        if cmd is None:
            #user tried to send some sort of command (a message with .,#, or *, but nothing after)
            self.__reply(msg,"command-not-understood")
            return True
        else:
            cmd=cmd.strip()

        if rest is not None:
            rest=rest.strip()

        # Now match the possible command to ones we know
        cmd_match=self.cmd_matcher.match(cmd,with_data=True)

        if len(cmd_match)==0:
            # no command match
            self.__reply(msg,"command-not-understood")
            return True

        if len(cmd_match)>1:
            # too many matches!
            self.__reply(msg, 'command-not-understood %(sug_1)s %(sug_rest)s', \
                              { 'sug_1':', '.join([t[0] for t in cmd_match[:-1]]),
                                'sug_rest':cmd_match[-1:][0][0]})
            return True
        #
        # Ok! We got a real command
        #
        cmd,data=cmd_match[0]
        #arg=msg_text[msg_match.end():]

        # set the senders default language, if not sent
        if msg.sender.locale is None:
            msg.sender.locale=data['lang']
            msg.sender.save()
        return data['func'](msg,arg=rest)

    def outgoing(self, msg):
        # TODO
        # create a ForumMessage class
        # log messages with associated domain
        # report on dashboard
        pass
        

    ####################
    # Command Handlers #
    ####################
    def help(self, msg,arg=None):
        if arg is not None and len(arg)>0:
            # see if it is a language and send help 
            # for that lang
            langs=self.lang_matcher.match(arg,with_data=True)
            if len(langs)==1:
                self.__reply(msg, "help-with-commands_%s" % langs[0][1])
                return True
            else:
                # send the list of available langs by passing
                # to the 'lang' command handler
                return self.help(msg)
            
        self.__reply(msg, "help-with-commands")
        return True

    @passwordProtectedCmd
    def create_village(self, msg, arg=None):
        self.debug("SMSFORUM:CREATEVILLAGE")        
        if arg is None or len(arg)<1:
            self.__reply(msg, "create-village-fail_no-village-name")
            return True
        else:
            village = arg

        if len(Village.objects.filter(name=village)) != 0:
            self.__reply(msg, "create-village-fail_village-already-exists %(village)s", {'village':village})
            return True
        try:
            # TODO: add administrator authentication
            if len(village) > MAX_VILLAGE_NAME_LEN:
                self.__reply(msg, "create-village-fail_name-too-long %(village)s %(max_char)d", \
                             {'village':village, 'max_char':MAX_VILLAGE_NAME_LEN} )
                return True
            ville = Village(name=village)
            ville.save()
            # self.village_matcher.add_target((village,ville))
            self.__reply(msg, "create-village-success %(village)s", {'village':village} )
        except:
            self.debug( traceback.format_exc() )
            traceback.print_exc()
            self.__reply(msg, "internal-error")

        return True
             
    def member(self,msg,arg=None):
        try:
            villages=villages_for_contact(msg.sender)
            if len(villages)==0:
                self.__reply(msg, "member-fail_not-member-of-village")
            else:
                village_names = ', '.join([v.name for v in villages])
                txt = "member-success %(village_names)s"
                if len(villages)>5: 
                    villages = villages[0:5]
                    txt = "member-success_long-list %(village_names)s"
                self.__reply(msg, txt, {"village_names":village_names})
        except:
            traceback.print_exc()
            self.debug( traceback.format_exc() )
            rsp= _st(msg.sender,"internal-error")
            self.debug(rsp)
            self.__reply(msg,rsp)
        return True

    @passwordProtectedCmd
    def community_members(self,msg,arg=None):
        if arg is None or len(arg)==0:
            self.__reply(msg, "citizens-fail_no-village")
            return True

        villes=self.__get_village_matcher().match(arg,with_data=True)
        if len(villes)==0:
            self.__reply(msg, "village-not-known %(unknown)s", {'unknown':arg})
            return True

        for name,ville in villes:
            members=[c.get_signature(max_len=10) for c in \
                         ville.flatten(klass=Contact)]
            if len(members)>20: 
                members = members[0:20]
                txt = 'citizens-success_long-list %(village)s %(citizens)s'
            else:
                txt = 'citizens-success %(village)s %(citizens)s'
                
            self.__reply(msg, txt, {'village':name, 'citizens':','.join(members)})
        return True

    @passwordProtectedCmd
    def destroy_community(self,msg,arg=None):
        if arg is None or len(arg)==0:
            self.__reply(msg, "remove-fail_no-village")
            return True

        try:
            # EXACT MATCH ONLY!
            ville=Village.objects.get(name=arg)
            ville.delete()
            # self.village_matcher.remove_target(arg)
            self.__reply(msg, "remove-success %(village)s", {'village': arg})
            return True
        except:
            rsp= _st(msg.sender,"village-not-known %(unknown)s") % {'unknown':arg} 
            self.debug(rsp)
            self.__reply(msg,rsp)
        return True

            
    def register_name(self,msg,arg=None):
        if arg is None or len(arg)==0:
            self.__reply(msg,"name-acknowledge %(name)s",
                         {'name':msg.sender.common_name})
            return True

        name=arg
        try:
            if len(name) > MAX_CONTACT_NAME_LEN:
                self.__reply(msg, "name-register-fail_name-too-long %(name)s %(max_char)d", \
                             {'name':name, 'max_char':MAX_CONTACT_NAME_LEN} )
                return True
            msg.sender.common_name = name
            msg.sender.save()
            rsp=_st(msg.sender, "name-register-success %(name)s") % {'name':msg.sender.common_name}
            self.__reply(msg,rsp)
        except:
            traceback.print_exc()
            self.debug( traceback.format_exc() )
            rsp= _st(msg.sender, "internal-error")
            self.debug(rsp)
            self.__reply(msg,rsp)

        return True

    def join(self,msg,arg=None):
        if arg is None or len(arg)==0:
            return self.__suggest_villages(msg)
        else:
            village=arg

        try:
            matched_villes=self.__get_village_matcher().match(village,with_data=True)
            # send helpful message if 0 or more than 1 found
            num_villes=len(matched_villes)
            # unzip data from names if can
            if num_villes>0:
                village_names,villages=zip(*matched_villes)

            if num_villes==0 or num_villes>1:
                if num_villes==0:
                    return self.__suggest_villages(msg)
                else:
                    # use all hit targets
                    rsp=_st(msg.sender, "village-not-found %(suggested)s") % \
                        {"suggested": ', '.join(village_names)}
                    self.__reply(msg,rsp)
                    return True
            
            # ok, here we got just one
            assert(len(villages)==1)
            villages[0].add_children(msg.sender)
            rsp=_st(msg.sender, "join-success %(village)s") % {"village": village_names[0]}
            self.debug(rsp)
            self.__reply(msg,rsp)
        except:
            traceback.print_exc()
            self.debug( traceback.format_exc() )
            rsp=_st(msg.sender, "internal-error")
            self.debug(rsp)
            self.__reply(msg,rsp)
        return True
            
    def blast_direct(self, msg, address, text):
        """
        find the matching people, groups. 
         
        Consider only matches that return ONE result!
        
        Otherwise people might accidentally send messages
        far wider than expected!
        
        The direct messaging to Contacts currently uses
        'common_name' which is not guaranteed unique,
        so if two people have the same name, the returned set
        will never be unique, and you will not be allowed to
        send to them.
        
        a system that wants to really implement Twitter like
        DM needs to either enforce uniqueness on common_name
        or use the 'unique_id' field in stead.
        
        Either way people will need to register a 'username'
        like handle in the field used for the match.
        
        """
        contacts=[(c.common_name, c) for c in Contact.objects.all()]
        cont_matcher=BestMatch(targets=contacts)
        found=MultiMatch(self.__get_village_matcher(),cont_matcher).\
            match(address,with_data=True)
        
        if len(found)==0:
            self.__reply(
                msg,
                'direct-blast-fail_recipient-not-found %(recipient)s',
                {'recipient':address}
                )
        elif len(found)>1:
            names,objs=zip(*found)
            self.__reply(
                msg,
                'direct-blast-fail_too-many-recipients %(recip_1)s and %(recip_rest)s',
                { 'recip_1':names[0], 'recip_rest': ', '.join(names[1:])}
                )
        else:
            # got one person or village!
            name,obj=found[0] # found is an array of tuples (name,obj)

            # prep the outbound message
            ok,out_text,enc=self.__prep_blast_message(msg,text,[name])
            if not ok:
                # oops, too long, __prep... already responded to 
                # user, so we'll just return
                return True

            rsp= _st(msg.sender, "direct-blast-acknowledge %(text)s %(recipient)s") % \
                {'recipient':name,'text':out_text}
 
            self.debug('REPSONSE TO BLASTER: %s' % rsp)
            self.__reply(msg,rsp)

            if isinstance(obj, Village):
                self.__blast_to_villages([obj],msg.sender,out_text)
            else:
                assert(isinstance(obj, Contact))
                self.__blast_to_contact(obj,out_text)
        return True

    def blast(self, msg):
        """Takes actual Contact objects"""
        self.debug("SMSFORUM:BLAST")

        #find all villages for this sender
        villes = villages_for_contact(msg.sender)
        if len(villes)==0:
            rsp=_st(msg.sender, "blast-fail_not-member-of-any-village")
            self.debug(rsp)
            self.__reply(msg,rsp)
            return True

        recips=[v.name for v in villes]
        ok,blast_text,enc=self.__prep_blast_message(msg,msg.text,recips)
        if not ok:
            # message was too long, prep already
            # sent a reply to the sender, so we just 
            # return out
            return True

        # respond to sender first because the delay between now 
        # and the last recipient can be long
        #
        # TODO: send a follow-up is message sending fails!
        rsp= _st(msg.sender, "blast-acknowledge %(text)s %(recipients)s") % \
            {'recipients':', '.join(recips),'text':msg.text.strip()} 
        self.debug('REPSONSE TO BLASTER: %s' % rsp)
        self.__reply(msg,rsp)

        return self.__blast_to_villages(villes,msg.sender,blast_text)
    
    def __prep_blast_message(self,msg,out_text,recipients):
        """
        helper function formats blast msg
        including signature and returns 3-ple
        of ( bool <message is good to send>, 
        formatted message w/signature, encoding required)
        
        If message is NOT good to send 3-ple will be
        (False, None, encoding)

        NOTE: If the message is too long this helper
        sends a reply to the msg.sender, so don't do
        that again!
        
        """

        # check for message length, and bounce messages that are too long
        #
        # since length depends on encoding, find out if we can send 
        # this GSM (160 chars) or UCS2 (70 chars)
        #
        # TODO: factor this somewhere better like the backend since
        # it's what knows the message size limits...
        #

        gsm_enc=True
        sender_sig=msg.sender.signature
        try:
            out_text.encode('gsm')
            sender_sig.encode('gsm')
        except:
            # Either message or sig needs UCS2 encoding
            gsm_enc=False
        finally:
            encoding,max_len=(\
                ('gsm',MAX_LATIN_BLAST_LEN) if gsm_enc \
                    else ('ucs2',MAX_UCS2_BLAST_LEN))
            
        if len(out_text)>max_len: 
            rsp= _st(msg.sender, "blast-fail_message-too-long %(msg_len)d %(max_latin)d %(max_unicode)d") % \
                {
                'msg_len': len(out_text),
                'max_latin': MAX_LATIN_BLAST_LEN,
                'max_unicode': MAX_UCS2_BLAST_LEN
                } 
            self.__reply(msg,rsp)
            return (False, None, encoding)

        # ok, we're long enough, lets make the blast text
        # we replace '%(sender)s' with '%(sender)s' so that
        # localized strings can put the sender where they want
        # we then do another subsitution after we pick the send signature
        blast_tmpl=_st(msg.sender, "blast-message_outgoing %(text)s %(recipients)s %(sender)s") % \
            { 'text':out_text, 'recipients':', '.join(recipients), 'sender': '%(sender)s'}

        #add signature
        tmpl_len=len(blast_tmpl)-10  # -10 accounts from sig placeholder ('%(sender)s')
        max_sig=max_len-tmpl_len
        sig=msg.sender.get_signature(max_len=max_sig,for_message=msg)
        blast_text = blast_tmpl % {'sender': sig}
        return (True, blast_text, encoding)

    def __blast_to_villages(self, villes, sender, text):
        """Takes actual village objects"""
        if villes is None or len(villes)==0:
            return True

        recipients=set()
        for ville in villes:
            recipients.update(ville.flatten(klass=Contact))
            
        # now iterate every member of the group we are broadcasting
        # to, and queue up the same message to each of them
        for recipient in recipients:
            if recipient != sender:
                self.__blast_to_contact(recipient,text)
        vnames = ', '.join([v.name for v in villes])
        self.debug("success! %(villes)s recvd msg: %(txt)s" % { 'villes':vnames,'txt':text})
        return True

    def __blast_to_contact(self, contact, text):
        """Returns True is message sent"""
        if contact.can_receive:
            self.debug('Blast msg: %s to: %s' % (text,contact.signature))
            # TODO: move to lib/pygsm/gsm.py
            # currently just log messages that are too long
            # since these are not handled properly in modem
            self._check_message_length(text)
            contact.send_to(text)
            return True
        else:
            return False

    def leave(self,msg,arg=None):
        self.debug("SMSFORUM:LEAVE: %s" % arg)
        try:
            villages=[]
            if arg is not None and len(arg)>0:
                village_tupes = self.__get_village_matcher().match(arg, with_data=True)
                if len(village_tupes)>0:
                    villages = zip(*village_tupes)[1] # the objects
            else:
                villages = villages_for_contact(msg.sender)
            if len(villages)>0:
                names = list()
                for ville in villages:
                    ville.remove_children(msg.sender)
                    names.append(ville.name)
                self.__reply(msg, "leave-success %(villages)s",
                             { "villages": ','.join(names)})
            else:
                if arg is not None and len(arg)>0:
                    self.__reply(msg, "leave-fail_village-not-found %(village)s", {'village':arg})
                else:
                    self.__reply(msg, "leave-fail_not-member-of-village")
        except:
            # something went wrong - at the
            # moment, we don't care what
            traceback.print_exc()
            self.debug( traceback.format_exc() )
            self.__reply(msg, "internal-error")

        return True

    def lang(self,msg,arg=None):
        name=arg
        self.debug("SMSFORUM:LANG:Current locale: %s" % msg.sender.locale)
        
        def _return_all_langs():
            # return available langs
            langs_sorted=[l[0] for l in _G['SUPPORTED_LANGS'].values()]
            langs_sorted.sort()
            rsp=_st(msg.sender, "language-set-fail_code-not-understood %(langs)s") % \
                { 'langs':', '.join(langs_sorted)}
            self.__reply(msg,rsp)
            return True

        if name is None or len(name)==0:
            return _return_all_langs()
        
        # see if we have that language
        langs=self.lang_matcher.match(name.strip(),with_data=True)
        if len(langs)==1:
            name,code=langs[0]
            msg.sender.locale=code
            msg.sender.save()
            rsp = _st(msg.sender, 'language-set-success %(lang)s') % { 'lang': name }
            self.__reply(msg,rsp)
            return True       
        else:
            # invalid lang code, send them a list
            return _return_all_langs()

            
    #
    # Private helpers
    # 
    def __reply(self,msg,reply_text,format_values=None):
        """
        Formats string for response for message's sender 
        the message's associated sender.

        """
        reply_text=_st(msg.sender,reply_text) 
        if format_values is not None:
            try:
                reply_text = reply_text % format_values
            except TypeError:
                err="Not all format values: %r were used in the string: %s" %\
                    (format_values, reply_text)
                self.error(err)
        # TODO: move to lib/pygsm/gsm.py
        # currently just log messages that are too long
        # since these are not handled properly in modem
        self._check_message_length(reply_text)
        msg.sender.send_response_to(reply_text)
    
    def __suggest_villages(self,msg):
        """helper to send informative messages"""
        # pick some names from the DB
        village_names = [v.name for v in Village.objects.all()[:3]]
        if len(village_names) == 0:
            village_names = _st(msg.sender,"village_name")
        else: 
            village_names=', '.join(village_names)
        self.__reply(msg,"village-not-found %(suggested)s", {"suggested": village_names})
        return True

    def __loadFixtures(self):
        pass

    def __log_incoming_message(self,msg,domains):
        #TODO: FIX THIS so that it logs for all domains
        if domains is None or len(domains)==0:
            return

        #msg.persistent_msg should never be none if app.logger is used
        #this is to ensure smsforum does not fail even if logger fails...
        if hasattr(msg,'persistent_msg'):
            msg.persistent_msg.domain = domains[0]
            msg.persistent_msg.save()

    def _check_message_length(self, text):
        """
        This function DOES NOT belong here - a temporary measure until 
        rapidsms has a good api for backends to speak to router
        
        checks message length < 160 if gsm, else <70 if ucs-2/utf16
        """

        gsm_enc=True
        try:
            text.encode('gsm')
        except:
            gsm_enc=False
        finally:
            encoding,max_len=(\
                ('gsm',MAX_LATIN_SMS_LEN) if gsm_enc \
                    else ('ucs2',MAX_UCS2_SMS_LEN))
        
        if len(text)>max_len:
            err= ("ERROR: %(encoding)s MESSAGE OF LENGTH '%(msg_len)d' IS TOO LONG. Max is %(max)d.") % \
                         {
                'encoding': encoding,
                'msg_len': len(text),
                'max': MAX_LATIN_SMS_LEN if encoding=='gsm' else MAX_UCS2_SMS_LEN
                } 
            self.error(err)
            return False
        return True