Login

Unfuddle-style post-commit emails - tied to a specific Django project.

Author:
jsandell
Posted:
September 20, 2008
Language:
Python
Version:
1.0
Score:
3 (after 3 ratings)

I needed a quick way to send e-mails to other people involved in a project when I committed code - so that designers would know that templatetag x was available (or fixed), etc - and am really fond of how unfuddle handles their subversion notifications. This isn't nearly as polished as theirs, but I figured it was a good place to start.

Hope someone else finds this as handy as I have.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/python
# post-commit.py
# symlinked in /path/to/repository/hooks/post-commit

import pysvn
import sys
import os
from datetime import datetime

# Begin Django bootstrap code

# You'll need to change this to match your own specific host /
# development path.

host_dir = '/var/www/hosts/example.com' 
site_dir = '%s/site' % host_dir
site_root = '%s/root' % site_dir

# I keep various versions of Django, and supporting Django apps,
# outside of the normal Python path. If you install via setup.py
# you might not need to loop this.

for p in ('/usr/local/codebase/framework', 
          '/usr/local/codebase/apps',
          site_root,
          '%s/project' % site_root):
  sys.path.append(p)

os.environ['DJANGO_SETTINGS_MODULE'] = 'project.settings'

# End Django bootstrap code

def getChangedFiles(path, revno):
  """
    Query svn repository for changes in a given changeset.

  """

  client = pysvn.Client()
  revision = pysvn.Revision(pysvn.opt_revision_kind.number, revno)
  previous = pysvn.Revision(pysvn.opt_revision_kind.number, int(revno)-1)
  changes = client.diff_summarize(
      url_or_path1 = path,
      revision1 = previous,
      revision2 = revision,
  )

  formatted = []

  if changes:
    for c in changes:
      # Create a list of two-tuples, containing the svn 'action'
      # (add, delete, modify, etc.) and the file that's been acted upon.
      formatted.append(('%s' % c.summarize_kind, '%s' % c.path))

  return tuple(formatted)

if __name__ == '__main__':

  # We should get something like 
  # /path/to/repository <revision number>
  # i.e., "/var/svn/sites/apps 46"
  #
  # argv[0], of course, is our script name.

  assert len(sys.argv) == 3

  # Django imports

  from django.core.mail import EmailMessage
  from django.template import Context, Template
  from django.conf import settings
  from django.contrib.sites.models import Site

  # Load the svn client
  client = pysvn.Client()
  
  # Create a "proper" svn url
  repo_url = 'file://%s' % sys.argv[1] 

  # Get our revision number.
  rev = sys.argv[2]

  # Send our url and the revision number to getChangedFiles
  changes = getChangedFiles(repo_url, rev)

  # Only act if there have been changes.
  if changes:
    # Attempt to ascertain which site this is for.
    site = Site.objects.all(pk=settings.SITE_ID)

    # Get the 'from' email setting from settings.py
    from_email = settings.DEFAULT_FROM_EMAIL

    # I'm pulling from 'MANAGERS', which by default is set from
    # 'ADMINS', but it could just as easily pull from a database
    # query.

    admin_list = [mail_tuple[1] for mail_tuple in settings.MANAGERS]

    # Get the commit metadata - author, commit date, etc.
    log = client.log(repo_url,
                    revision_start = pysvn.Revision(pysvn.opt_revision_kind.head),
                    revision_end=pysvn.Revision(pysvn.opt_revision_kind.number, 
                    int(rev)))[0]

    # Convert the float timestamp into a UTC datetime object.
    stamp = datetime.utcfromtimestamp(log['date'])

    # Further convert it into something more human readable.
    friendly_date = stamp.strftime('%A %b %d, %Y')
    friendly_time = stamp.strftime('%I:%M %P')

    # Get the author / committer.
    author = log['author']

    # Get the commit message
    message = log['message']

    # Build our template context.
    c = Context(dict(
      site = site,
      author = author,
      date = friendly_date,
      time = friendly_time,
      divider = '-' * 67,
      revision = rev,
      changes = changes,
      message = message,
    ))

    # Create our subject and body templates. We can also use normal
    # templates for this, but that goes beyond the context of this
    # snippet.

    s = Template("Commit for {{ site }}")
    b = Template("""Revision: {{ revision }}
Author: {{ author }}
Date: {{ date }} {{ time }}
{{ divider }}
{{ message|default:"(no commit message)" }}
{% for action, path in changes %}
{{ action|capfirst }} {{ path }}{% endfor %}
""")

    # Render our templates.
    subject = s.render(c)
    body = b.render(c)

    # Set up our email message.
    mail = EmailMessage(subject, body, from_email, admin_list)

    # Send using text. Change to 'html' to use rich formatting.
    mail.content_subtype = 'plain'

    # Send our commit information!
    mail.send()

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

Please login first before commenting.