Added a ton.. haven't even tested. Needs work for sure.
A trello_broker/api.py +55 -0
@@ 0,0 1,55 @@ 
+import trello
+
+
+def get_client(trello_token):
+    return trello.TrelloApi(trello_token.api_key, token=trello_token.api_token)
+
+
+def get_all_trello_boards(trello_token, client=None):
+    ''' Get JSON data for all Trello boards the given
+        token has access to.
+    '''
+    if client is None:
+        client = get_client(trello_token)
+    user_data = client.members.get('me')
+    data = client.members.get_board('me')  # Personal boards
+    for oid in user_data['idOrganizations']:
+        # Cycle through all organization boards
+        data += client.organizations.get_board(oid)
+    return data
+
+
+def get_all_trello_board_lists(trello_token, board_id, client=None):
+    ''' Get JSON data for all lists on a given Trello board.
+    '''
+    if client is None:
+        client = get_client(trello_token)
+    return client.boards.get_list(board_id)
+
+
+def get_card_from_board(trello_token, board_id, card_id, client=None):
+    ''' Get Card JSON data from short id (ie, #123) from specific board.
+    '''
+    if client is None:
+        client = get_client(trello_token)
+    return client.boards.get_card_idCard(card_id, board_id)
+
+
+def add_comment_to_card(trello_token, card_id, comment, client=None):
+    if client is None:
+        client = get_client(trello_token)
+    return client.cards.new_action_comment(card_id, comment)
+
+
+def move_card(trello_token, card_id, new_list_id, client=None):
+    ''' Move a card from one list to another.
+    '''
+    if client is None:
+        client = get_client(trello_token)
+    return client.cards.update_idList(card_id, new_list_id)
+
+
+def archive_card(trello_token, card_id, client=None):
+    if client is None:
+        client = get_client(trello_token)
+    return client.cards.update_closed(card_id, True)

          
A => trello_broker/management/__init__.py +0 -0

A => trello_broker/management/commands/__init__.py +0 -0

A => trello_broker/management/commands/add_trello_token.py +0 -0

A => trello_broker/management/commands/populate_trello_boards.py +0 -0

A trello_broker/models.py +131 -0
@@ 0,0 1,131 @@ 
+from django.db import models
+from django.utils import timezone
+from django.utils.translation import ugettext_lazy as _
+
+
+class BaseModel(models.Model):
+    created = models.DateTimeField(default=timezone.now)
+    modified = models.DateTimeField(default=timezone.now)
+
+    class Meta:
+        abstract = True
+
+    def __unicode__(self):
+        return getattr(self, 'name', u'')
+
+    def save(self, *args, **kwargs):
+        self.modified = timezone.now()
+        super(BaseModel, self).save(*args, **kwargs)
+
+
+class TrelloToken(BaseModel):
+    name = models.CharField(
+        max_length=100,
+        help_text=_('Name of the account. Used for internal identification'),
+    )
+    api_key = models.CharField(
+        max_length=100,
+        help_text=_('Get this from https://trello.com/1/appKey/generate'),
+    )
+    api_token = models.CharField(
+        max_length=100,
+        help_text=_(
+            'Get this from https://trello.com/1/authorize?'
+            'key=<your api_key>&name=<Your+App+Name>&expiration=never&'
+            'response_type=token&scope=read,write'
+        ),
+    )
+
+
+class TrelloBoard(BaseModel):
+    trello_token = models.ForeignKey('trello_broker.TrelloToken')
+    name = models.CharField(max_length=100, blank=True)
+    trello_id = models.CharField(max_length=100)
+
+
+class TrelloList(BaseModel):
+    STATUS_ACTIVE = 1
+    STATUS_ARCHIVED = 2
+    STATUS_CHOICES = (
+        (STATUS_ACTIVE, _('Active')),
+        (STATUS_ARCHIVED, _('Archived')),
+    )
+    trello_board = models.ForeignKey(
+        'trello_broker.TrelloBoard',
+        related_name='trello_lists',
+    )
+    name = models.CharField(max_length=100)
+    status = models.PositiveIntegerField(
+        default=STATUS_ACTIVE,
+        choices=STATUS_CHOICES,
+    )
+    trello_id = models.CharField(max_length=100)
+
+    def __unicode__(self):
+        return u'{0}: {1}'.format(self.trello_board.name, self.name)
+
+
+class BitBucketRepo(BaseModel):
+    name = models.CharField(
+        max_length=100,
+        help_text=_('Name of the repo. Used for internal identification'),
+    )
+    slug = models.SlugField(
+        max_length=100,
+        help_text=_('Slug ID given by BitBucket for this repository.'),
+    )
+    trello_board = models.ForeignKey('trello_broker.TrelloBoard')
+
+    @property
+    def fix_rule(self):
+        if not hasattr(self, '_fix_rule'):
+            self._fix_rule = self.rules.filter(
+                action=BitBucketRule.ACTION_FIXES,
+            ).first()
+        return self._fix_rule
+
+    @property
+    def ref_rule(self):
+        if not hasattr(self, '_ref_rule'):
+            self._ref_rule = self.rules.filter(
+                action=BitBucketRule.ACTION_REFERENCED,
+            ).first()
+        return self._ref_rule
+
+
+class BitBucketRule(BaseModel):
+    ACTION_REFERENCED = 1
+    ACTION_FIXES = 2
+    ACTION_CHOICES = (
+        (ACTION_REFERENCED, _('Referenced')),
+        (ACTION_FIXES, _('Fixes / Closes')),
+    )
+    repo = models.ForeignKey(
+        'trello_broker.BitBucketRepo',
+        related_name='rules',
+    )
+    action = models.PositiveIntegerField(
+        default=ACTION_REFERENCED,
+        choices=ACTION_CHOICES,
+    )
+    update = models.BooleanField(
+        default=True,
+        help_text=_('If checked, card will be updated with commit comment.'),
+    )
+    archive = models.BooleanField(
+        default=False,
+        help_text=_('If checked, card will be archived.'),
+    )
+    move = models.BooleanField(
+        default=False,
+        help_text=_('If checked, card will be moved '
+                    'to specified Trello List.'),
+    )
+    trello_list = models.ForeignKey(
+        'trello_broker.TrelloList',
+        null=True,
+        blank=True,
+    )
+
+    def __unicode__(self):
+        return u'{0}: {1}'.format(self.repo.name, self.get_action_display())

          
A => trello_broker/templates/trello_broker/update_message.txt +1 -0
@@ 0,0 1,1 @@ 
+Commit ```{{ changeset }}``` committed by {{ author }} with the message ```{{ commit_message }}``` - View at: {{ commit_url }}

          
A => trello_broker/urls.py +9 -0
@@ 0,0 1,9 @@ 
+from django.conf.urls import patterns, url
+from .views import BitBucketPostView
+
+
+urlpatterns = patterns('trello_broker.views',
+    url(r'^$',
+        BitBucketPostView.as_view(),
+        name='trello_broker_post'),
+)

          
A trello_broker/utils.py +92 -0
@@ 0,0 1,92 @@ 
+import re
+from urlparse import urljoin
+from django.conf import settings
+from django.template.loader import render_to_string
+from . import api
+
+
+# Based on pattern from https://bitbucket.org/sntran/trello-broker
+re_pattern = re.compile(r'''
+    (               # start capturing the verb
+    fix             # contains 'fix'
+    | close         # or 'close'
+    |               # or just to reference
+    )               # end capturing the verb
+    e?              # maybe followed by 'e'
+    (?:s|d)?        # or 's' or 'd', not capturing
+    \s              # then a white space
+    [#]             # and '#' to indicate the card
+    ([0-9]+)        # with the card's short id.
+    ''',
+    re.VERBOSE | re.IGNORECASE,
+)
+
+
+def process_commits(repo, json_data):
+    ''' Function to parse commit messages and act on
+        BitBucketRule sets.
+    '''
+    base_repo_url = u'{0}{1}'.format(
+        json_data['canon_url'],
+        json_data['repository']['absolute_url'],
+    )
+    base_repo_url += '/' if base_repo_url[-1] != '/' else ''
+    base_commit_url = urljoin(base_repo_url, 'commits/')
+    trello_token = repo.trello_board.trello_token
+
+    for commit in json_data['commits']:
+        proc_cards = []
+        msg = commit['message']
+        context = {
+            'author': commit['author'],
+            'author_full': commit['raw_author'],
+            'changeset': commit['node'],
+            'changeset_full': commit['raw_node'],
+            'commit_url': urljoin(base_commit_url, commit['raw_node'])
+            'commit_message': msg,
+        }
+
+        for action, card_id in re_pattern.findall(msg):
+            if card_id in proc_cards:
+                # Referenced more than once in the commit msg
+                continue
+
+            proc_cards.append(card_id)
+            use_rule = 'fix_rule' \
+                if action.lower() in ['fix', 'close'] else 'ref_rule'
+            rule = getattr(repo, use_rule)
+            if not rule:
+                continue
+
+            # Get Card information
+            card = api.get_card_from_board(
+                trello_token,
+                card_id,
+                repo.trello_board.trello_id,
+            )
+            full_card_id = card['id']
+
+            # Let's process the rule
+            if rule.update:
+                # Update the card.
+                card_msg = render_to_string(
+                    'trello_broker/update_message.txt',
+                    context,
+                )
+                api.add_comment_to_card(
+                    trello_token,
+                    full_card_id,
+                    card_msg,
+                )
+
+            if rule.move:
+                # Move the card
+                api.move_card(
+                    trello_token,
+                    full_card_id,
+                    rule.trello_list.trello_id,
+                )
+
+            if rule.archive:
+                # Archive the card
+                api.archive_card(trello_token, full_card_id)

          
A trello_broker/views.py +38 -0
@@ 0,0 1,38 @@ 
+import json
+from django.conf import settings
+from django.views.generic import View
+from django.shortcuts import get_object_or_404
+from django.http import HttpResponse, HttpResponseBadRequest, Http404
+from .models import BitBucketRepo
+from .utils import process_commits
+
+if getattr(settings, 'TRELLO_BROKER_USE_CELERY', False):
+    from .tasks import celery_process_commits
+else:
+    celery_process_commits = None
+
+
+def trello_distribute(repo, json_data):
+    ''' Helper function that decides how to process
+        Trello interaction (celery or not)
+    '''
+    if celery_process_commits is not None:
+        celery_process_commits.delay(repo.id, json_data)
+    else:
+        process_commits(repo, json_data)
+
+
+class BitBucketPostView(View):
+    def get(self, request, *args, **kwargs):
+        return HttpResponseBadRequest()
+
+    def post(self, request, *args, **kwargs):
+        try:
+            json_data = json.loads(request.body)
+            repo_slug = json_data['respository']['slug']
+        except (ValueError, KeyError):
+            return HttpResponseBadRequest()
+
+        repo = get_object_or_404(BitBucketRepo, slug=repo_slug)
+        trello_distribute(repo, json_data)
+        return HttpResponse('OK')