Created
August 11, 2011 22:58
-
-
Save octaflop/1141018 to your computer and use it in GitHub Desktop.
Excel Model Migration with PostgreSQL error
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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