Skip to content

Instantly share code, notes, and snippets.

@octaflop
Created August 11, 2011 22:58
Show Gist options
  • Save octaflop/1141018 to your computer and use it in GitHub Desktop.
Save octaflop/1141018 to your computer and use it in GitHub Desktop.
Excel Model Migration with PostgreSQL error
# models.py
from django.db import models
from django.db.utils import IntegrityError
from tinymce.models import HTMLField
import datetime
from django.contrib.auth.models import User, UserManager, Permission
import re
from django.utils.translation import ugettext as _
from django.contrib.localflavor.us.models import PhoneNumberField
from django.template.defaultfilters import slugify
# from http://djangosnippets.org/snippets/690/
def unique_slugify(instance, value, slug_field_name='slug', queryset=None,
slug_separator='-'):
"""
Calculates and stores a unique slug of ``value`` for an instance.
``slug_field_name`` should be a string matching the name of the field to
store the slug in (and the field to check against for uniqueness).
``queryset`` usually doesn't need to be explicitly provided - it'll default
to using the ``.all()`` queryset from the model's default manager.
"""
slug_field = instance._meta.get_field(slug_field_name)
slug = getattr(instance, slug_field.attname)
slug_len = slug_field.max_length
# Sort out the initial slug, limiting its length if necessary.
slug = slugify(value)
if slug_len:
slug = slug[:slug_len]
slug = _slug_strip(slug, slug_separator)
original_slug = slug
# Create the queryset if one wasn't explicitly provided and exclude the
# current instance from the queryset.
if queryset is None:
queryset = instance.__class__._default_manager.all()
if instance.pk:
queryset = queryset.exclude(pk=instance.pk)
# Find a unique slug. If one matches, at '-2' to the end and try again
# (then '-3', etc).
next = 2
while not slug or queryset.filter(**{slug_field_name: slug}):
slug = original_slug
end = '%s%s' % (slug_separator, next)
if slug_len and len(slug) + len(end) > slug_len:
slug = slug[:slug_len-len(end)]
slug = _slug_strip(slug, slug_separator)
slug = '%s%s' % (slug, end)
next += 1
setattr(instance, slug_field.attname, slug)
def _slug_strip(value, separator='-'):
"""
Cleans up a slug by removing slug separator characters that occur at the
beginning or end of a slug.
If an alternate separator is used, it will also replace any instances of
the default '-' separator with the new separator.
"""
separator = separator or ''
if separator == '-' or not separator:
re_sep = '-'
else:
re_sep = '(?:-|%s)' % re.escape(separator)
# Remove multiple instances and if an alternate separator is provided,
# replace the default '-' separator.
if separator != re_sep:
value = re.sub('%s+' % re_sep, separator, value)
# Remove separator from the beginning and end of the slug.
if separator:
if separator != '-':
re_sep = re.escape(separator)
value = re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value)
return value
class Member(models.Model):
"""
Members are imported from the membership Excel docs
"""
#user = models.OneToOneField(User)
slug = models.SlugField(_("URL-friendly name"), max_length=80, unique=True)
REGION_CHOICES = (
('other', _("Other")),
('fraservalley', _("Fraser Valley")),
('interior', _("Interior")),
('vancoast', _("Vancouver Coastal")),
('vanisle', _("Vancouver Island")),
('northern', _("Northern")),
('unknown', _("Unknown")),
)
## Fields ordered by EXCEL ROWS
# A
memnum = models.IntegerField(_("FRP-BC Member Number"))
# B, C
renewal = models.DateField(_("Renewal Due Date"), blank=True, null=True)
# D
region = models.CharField(choices=REGION_CHOICES, max_length=12)
# E
agency = models.CharField(_("Member-Agency Name"), max_length=200)
# F
address = models.CharField(_("Street Address"), max_length=200)
# G
city = models.CharField(_("City/Town"), max_length=150)
PROVINCE_CHOICES = (
("BC", _("British Columbia")),
("AB", _("Alberta")),
("SK", _("Saskatchewan")),
("MB", _("Manitoba")),
("ON", _("Ontario")),
("QC", _("Quebec")),
("PE", _("Prince Edward Island")),
("NL", _("Newfoundland and Labrador")),
("NS", _("Nova Scotia")),
("NB", _("New Brunswick")),
("YT", _("Yukon Territory")),
("NT", _("Northwest Territories")),
("NU", _("Nunavut")),
)
# H
province = models.CharField(_("Province"), max_length=2, choices=PROVINCE_CHOICES)
# I the standard is only 6, but whatever
postal_code = models.CharField(_("Postal Code"), max_length=7)
@property
def full_address(self):
ret = \
"""
%(agency)s
%(street)s
%(city)s, %(province)s
%(postalcode)s
""" % {
'agency': self.agency,
'street': self.address,
'city': self.city,
'province': self.province,
'postalcode': self.postal_code,
}
return ret
# J
agphone = PhoneNumberField(_("Agency Phone Number"), blank=True)
# K
agfax = PhoneNumberField(_("Agency Fax Number"), blank=True)
# L
agdirect = models.CharField(_("Agency Executive Director / ECD Program Manager"),
max_length=200, blank=True)
# M
dirphone = PhoneNumberField(_("Direct Phone Number"), blank=True)
# N
email = models.EmailField(_("Executive Director's Email"), blank=True)
# O
resname = models.CharField(_("FRP Name"), max_length=180)
# P
frpphone = PhoneNumberField(_("FRP Phone Number"), blank=True)
# Q perhaps make the coordinator a foreign key?
coordinator = models.CharField(_("FRP Coordinator / Manager"),
max_length=200, blank=True)
# R
coemail = models.EmailField(_("Coordinator's Email"), blank=True)
# S
website = models.URLField(_("Website"), blank=True)
# T
joint = models.BooleanField(_("Joint Member"))
# U
prior = models.BooleanField(_("FRP Canada Member prior to Joint Offer"))
# New Joint booleans
# V
newjoint2009 = models.BooleanField(_("New Joint Member (2009)"))
# W
newjoint2010 = models.BooleanField(_("New Joint Member (2010)"))
# X
newjoint2011 = models.BooleanField(_("New Joint Member (2011)"))
# Y
newbc2010 = models.BooleanField(_("New FRP-BC Member (2010)"))
# Z
frpbcfee = models.DecimalField(_("FRP-BC Fee"), max_digits=9,
decimal_places=2, blank=True)
# AA
jointmemfee = models.DecimalField(_("Joint Member Fee"), max_digits=9,
decimal_places=2, blank=True)
# AB
@property
def totalfee(self):
return self.frpbcfee + self.joinmemfee
# AC -- ## TK! check if this should be auto
updated = models.DateField(_("Updated"))
# AD
receipt = models.IntegerField(_("Reciept"), blank=True)
# AE
owecanada = models.DecimalField(_("Amount FRP-BC owes FRP Canada"),
blank=True, max_digits=9, decimal_places=2)
# AF
paidfrpc = models.BooleanField(_("Paid (FRPC)"))
# AG
owefrpbc = models.DecimalField(_("Amount FRP Canada owes FRP-BC"),
blank=True, max_digits=9, decimal_places=2)
# AH
paidfrpbc = models.BooleanField(_("Paid (FRPBC)"))
# AI
notes = models.TextField(_("Notes"), blank=True)
# added for new site
site_updated = models.DateField(_("Last Updated (On Site)"), blank=True)
description = HTMLField(_("Description (for directory)"), blank=True)
def save(self, *args, **kwargs):
slugstr = "%s %s" % (self.agency, self.region)
unique_slugify(self, slugstr)
self.updated = datetime.datetime.now()
self.site_updated = datetime.datetime.now()
super(Member, self).save(*args, **kwargs)
# Migration Command "migratemem.py"
from django.core.management.base import BaseCommand, CommandError
from memdir.models import Member
from django.db import models
from datetime import datetime
from decimal import Decimal
import xlrd
import os
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
class Error(Exception):
pass
class MigrateError(Error):
"""
This is raised when the migration fails
"""
def __init__(self, prev, next, msg):
self.prev = prev
self.next = next
self.msg = msg
class RegionError(Error):
def __init__(self, expr, msg):
self.expr = expr
self.msg = msg
def match_region(longregion):
lr = longregion
if lr == "Other":
return 'other'
elif lr == "Fraser Valley":
return 'fraservalley'
elif lr == "Interior":
return 'vancoast'
elif lr == "Vancouver Island":
return 'vanisle'
elif lr == "Northern":
return 'northern'
elif lr == "Vancouver Coastal":
return 'vancoast'
else:
return 'unknown'
"""
except RegionError as e:
print "There was no region found for the cell: %s" % e
"""
def getrenew(date):
if date == "Not Renewing":
return
else:
return datetime(*xlrd.xldate_as_tuple(date, 0))
def getfee(cellval):
if cellval.value == '':
return Decimal(0)
else:
return Decimal(str(cellval.value))
def yesbool(renew):
if renew == "YES":
return True
else:
return False
def migrate(row):
"""
The magical migration function
"""
mem = Member()
# A
mem.memnum = row[0].value
# B, C
mem.renewal = getrenew(row[2].value)
# D
mem.region = match_region(row[3].value)
# E
mem.agency = row[4].value
# F
mem.address = row[5].value
# G
mem.city = row[6].value
# H
mem.province = row[7].value
# I
mem.postal_code = row[8].value
# J
mem.agphone = row[9].value
# K
mem.agfax = row[10].value
# L
mem.agdirect = row[11].value
# M
mem.dirphone = row[12].value
# N
mem.email = row[13].value
# O
mem.resname = row[14].value
# P
mem.frpphone = row[15].value
# Q
mem.coordinator = row[16].value
# R
mem.coemail = row[17].value
# S
mem.website = "http://%s" % row[18].value
# T
mem.joint = yesbool(row[19].value)
# U
mem.prior = yesbool(row[20].value)
# V
mem.newjoint2009 = yesbool(row[21].value)
# W
mem.newjoint2010 = yesbool(row[22].value)
# X
mem.newjoint2011 = yesbool(row[23].value)
# Y
mem.newbc2010 = yesbool(row[24].value)
# Z
mem.frpbcfee = getfee(row[25])
# AA
mem.jointmemfee = getfee(row[26])
# AB -- AUTO 28 [27]
## AC -- AUTO TK 29 [28]
# AD
mem.receipt = int(getfee(row[27]))
# AE
mem.owecanada = getfee(row[28])
# AF
mem.paidfrpc = yesbool(row[29].value)
# AG
mem.owefrpbc = getfee(row[30])
# AH
mem.paidfrpbc = yesbool(row[31].value)
# AI
mem.notes = row[32].value
try:
mem.save()
except Error as e: #models.FieldError as e:
print "There was a field error: %s" % e
# raise MigrateError
#finally:
# raise MigrateError
class Command(BaseCommand):
args = '<None>'
help = 'Migrates excel to the database'
def handle(self, *args, **options):
book = xlrd.open_workbook("%s/../../../../assets/members.xls" % BASE_DIR)
sheet = book.sheets()[0]
ret = ""
for row in range(1, sheet.nrows):
try:
migrate(sheet.row(row))
ret += "No errors! WOOT!"
except MigrateError as e:
ret += "Migration failed with error: %s" % e.value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment