Converting FileMaker to Django
Around 1996 I got tired of using VI, BBEdit Lite, and Claris HomePage to create every page on my burgeoning web site. Every time I made a change to the style of the site, I had to go through each page and change the HTML surrounding the content (back then, we didn’t even have CSS).
At the time, FileMaker Pro was a great choice for databases on the Mac, and there wasn’t much of anything else. So I worked up a FileMaker database of my web site that allowed me to more easily relate folders to their contents, and to centralize common elements. AppleScripts made it easy to upload a page, a branch of the site, or all changed pages from within FileMaker to the web site via Anarchy.
That solution served me fairly well, but as time went on the important features to me in FileMaker stagnated while other more automatable and more standards-based options grew. Around the turn of the century I pretty much stopped updating the FileMaker section of the web site because it was comparatively more difficult to work with than the MySQL/PHP section of the web site that I’m writing this in.
Because of that, I haven’t upgraded my FileMaker past version 6. It is starting to show some cracks. The time has come to start migrating all of my FileMaker data before my FileMaker stops working, whether on some future OS or on an Intel Mac.
Most of that data, I’m moving into Django. It provides an easy-to-use interface to MySQL and SQLite; SQLite is a great choice for databases when I want the data in a single easily-managed file.
The goal is to be able to easily reposition my data; I want to be able to use it in PHP, to correlate it with other on-line data, to use it with any other scripting language I might need or any other web templating API. I want to be able to have scripts automatically update my data whether I'm logged in or not. FileMaker remains the best layout creator for printing, but I don’t print as often as I used to. Once the web and other Internet technologies come into play, FileMaker can be harder to use than ostensibly more difficult databases such as MySQL or SQLite.
A sample import script
Transferring from FileMaker (or some other GUI app) into SQLite or MySQL and Django, can be made much easier with appscript.
This is a simplified version of the import script that I used for importing web pages from FileMaker. This doesn’t import all of the fields in the FileMaker database, but from an example standpoint a lot of it was redundant. This is a working script, however.
[toggle code]
- #!/usr/bin/python
- #import FileMaker web pages to Django CMS
- import sys, os, optparse
- import appscript
- from MySQLdb import IntegrityError
- #import Django models
- sys.path.append("/Users/capvideo/Documents/CMS")
- os.environ['DJANGO_SETTINGS_MODULE'] = 'pages.settings'
- from pages.pages.models import Template, Page, Site
- parser = optparse.OptionParser()
- parser.add_option("-c", "--commit", dest="commit", action="store_true", default=False)
- (options, args) = parser.parse_args()
- templates = {}
- templates['Subpage'] = 'subpage.html'
- templates['None'] = 'none.html'
- templates['Wide'] = 'wide.html'
- templatecache = {}
- #get pages from FM
- dbname = "Space Pages"
- fm = appscript.app(id='com.filemaker.filemakerpro')
- fm.activate()
- fm.documents.open("Lisport:Users:capvideo:Documents:Web:"+dbname)
- db = fm.databases[dbname]
- db.records.show()
- db.sort(by=fm.fields['Title'])
- db.layouts["Export"].show()
- pagelist = fm.records()
- #delete existing data if we're re-importing
-
if options.commit:
- print "Deleting existing pages and page links from Django...",
- Page.objects.all().delete()
- print " done."
- parents = {}
- pages = {}
- #common paths and text
- replacements = {}
- replacements['<<SEARCHINC>>'] = '{{ library.searchinc }}'
- replacements['<<ETC>>'] = '{{ library.etc }}'
- replacements['<<HELP DESK>>'] = '{{ library.helpdesk }}'
-
def fixEncodings(text):
- fixed = text
-
for fromtext, totext in replacements.iteritems():
- fixed = fixed.replace(fromtext, totext)
- return fixed
-
for page in pagelist:
- title, slug, description, content, pageID, parentID, filename, folder, rank, timestamp, template, keywords, extension, site = page
- #create the data
- data = {}
- data['title']=fixEncodings(title)
- data['slug'] = slug
- data['description'] = fixEncodings(description)
- data['content'] = fixEncodings(content)
- data['keywords'] = keywords
- data['rank'] = rank
- data['edited'] = timestamp
- data['added'] = timestamp
-
if filename == 'index' and folder:
- data['filename'] = folder
-
elif filename:
- data['filename'] = filename
- data['extension'] = extension
-
if site:
- #sites have already been created manually
- data['site'] = Site.objects.get(title=site)
-
if template in templates:
- templateFile = templates[template]
-
if not templateFile in templatecache:
- templatecache[templateFile] = Template.objects.get(path=templateFile)
- data['myTemplate'] = templatecache[templateFile]
-
elif template:
- print "Cannot find template", template
-
if options.commit:
- djangopage = Page.objects.create(**data)
- #remember parents to set later once all pages are in the system
- pages[pageID] = djangopage
-
if int(parentID):
- parents[pageID] = parentID
-
else:
- print title
-
if parents:
-
for pageID, parentID in parents.iteritems():
- page = pages[pageID]
-
if parentID in pages:
- parent = pages[parentID]
- page.parent=parent
-
try:
- page.save()
-
except IntegrityError, message:
- print page.id, "has integrity error on adding parent", parent.id, "-", message
-
else:
- print page.title, pageID, "has a non-existent parent", parentID
-
for pageID, parentID in parents.iteritems():
This script loops through each record and imports the record into the Page model. It looks for the appropriate records from the Site and Template model and attaches them to the page. Each record (except one main home page) also has a parent record. It stores the FileMaker ID of the parent record for later, because the parent record might not have been created yet. After all records have been created, the script loops through each page and attaches its parent.
Some fields contained special codes that were replaced with common data. The text <<HELP DESK>>, for example would on publishing be replaced with contact info for help. In Django I have a library object that contains these common elements. So, fields that might contain those special codes are run through a function, fixEncodings, that replaces one with the other.
Some Django thoughts
- Use Django’s development server in a separate (terminal) window. It will reload source files such as models.py and views.py as soon as you save them. This lets you easily make changes to your models and views during the conversion process.
- manage.py runserver localhost:8000
- http://localhost:8000/admin/
- If you’re using the development server, you can “print” in models.py and views.py, and the printed text will appear in the server’s output.
- Break the conversion into steps, and organize the steps into pre-conversion and post-conversion steps. Things that involve you testing the new database structure and the conversion process are pre-conversion steps. Steps that involve you modifying the data in the new database, and which you would have to manually re-enter every time you re-import, are post-conversion tasks. Keep your pre-conversion list in your import script; keep your post-conversion list in views.py.
- While in the pre-conversion stage, continue editing all data in the old source, and re-import regularly.
- If you have a field for keeping track of when a record was added and when it was last updated (which I strongly recommend), disable the auto_now_add and auto_now options while you’re in the pre-conversion stage. Otherwise, the Django API will overwrite what you put there from the old database with the current time.
- As you edit your import script, you may notice that some things belong in separate tables. Now is a great time to make those changes.
- Use Django’s admin filters to track down strange categorization data: add the fields you are currently worried about to list_filter, and then track down what categories you’re using and how many records use them.
- You can construct Django admin links in models.py. Create a method for your model that returns a string, and put that method in list_display. You can use this, for example, to create your own navigational structure in the Django admin by linking to related records. For example, this will create a link that displays only the children of the current record:
[toggle code]
-
def childlink(self):
- childlist = len(self.children(keepUnDisplayed=True, keepHolder=False))
-
if childlist > 0:
-
if childlist == 1:
- text = "1 child"
-
else:
- text = str(childlist) + " children"
- link = '?o=2&parent__id__exact=' + str(self.id)
- text = '<a href="' + link + '">' + text + '</a>'
- return text
-
if childlist == 1:
-
else:
- return "None"
- childlink.short_description="Children"
- childlink.allow_tags = True
-
def childlink(self):
Some FileMaker/AppleScript thoughts
- Select your data in AppleScript, and then use appscript AppleScript translator to convert your AppleScript to Python.
- In FileMaker, make a simple vertical layout with just the data you need.
- Use calculation fields or the equivalent to modify data in the old source (such as FileMaker) if that’s easier than modifying it in Python.
- The web plug-in in FileMaker can convert special characters to their HTML-encoded equivalents.
- Django
- “Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.” Oh, the sweet smell of pragmatism.
- appscript
- Appscript is a high-level, user-friendly MacPython to Apple event bridge that allows you to control scriptable Mac OS X applications using ordinary Python scripts. Appscript makes MacPython a serious alternative to Apple’s own AppleScript language for automating your Mac.
More Django
- Reusing Django’s filter_horizontal
- Just as with pop-ups, it’s possible to use the built-in JavaScript for filtering multiple-selection popups on custom forms.
- Django formsets and date/time fields
- Date/Time fields in Django formsets appear to have incompatible default values, resulting in forms using them always looking as though they’ve got a new entry when they don’t.
- Multiple Input Fields with multiple inheritance
- We needed to display one TextField as either a TextInput or a Textarea, depending on the value in the field. Multiple inheritance makes it easy, if a bit wonky.
- Django tutorial mostly ready
- My long-promised Django tutorial is pretty much ready. It’s still designed around an in-person tutorial, but you should be able to get started using it even if you’re on your own.
- Django: Beyond the SQL
- Django is a great application framework for Python and web applications. You can use it to greatly speed up your database and application development both on the web and on the command line. This tutorial is currently a very rough draft; it probably won’t be very useful without the assistance of someone who knows Django running the tutorial. If I ever run this tutorial a second time, I’ll probably update it with screenshots to make it more usable for individuals.
- 22 more pages with the topic Django, and other related pages
More AppleScript
- Getting the selected playlist in iTunes
- It took a while to figure out how to get iTunes’s selected playlist as opposed to the current playlist in AppleScript.
- appscript AppleScript translator
- Those of us who like Applescript but want a solid command-line scripting environment no longer have to muddle through when using appscript. We can write in AppleScript and then translate to appscript.
- Play this song backwards in iTunes
- Using AppleScript and Quicktime, you can play any song backwards. Find out what Fred Schneider was saying on “Detour Thru Your Mind”.
- Create a web browser in AppleScript Studio
- AppleScript Studio is a powerful means of putting a graphic user interface onto AppleScript applications. Interface Builder lets us use any of the simple and complex controls that any other application can use.
- Run a script on inserting an audio CD
- You can run a script every time you insert an audio CD to ensure that iTunes is ready for import.
- Six more pages with the topic AppleScript, and other related pages
More appscript
- Cleaning iTunes track information
- Python and appscript make it easy to modify iTunes track information in batches—if you’re willing to get your hands dirty on the Mac OS X command line.
- appscript AppleScript translator
- Those of us who like Applescript but want a solid command-line scripting environment no longer have to muddle through when using appscript. We can write in AppleScript and then translate to appscript.
- Using appscript in Python to control GUI applications
- The appscript module for Python lets you control scriptable applications on the command line without having to coordinate your command-line script with your Applescript applications.
