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 AppleScript
- Find all parent mailboxes in macOS Mail
- The macOS Mail app seems to want to hide the existence of mailboxes and any sense of hierarchical storage. These two AppleScripts will help you find the full path to a selected message and open the message’s mailbox.
- JXA and AppleScript compared via HyperCard
- How does JXA compare to the AppleScript solution for screenshotting every card in HyperCardPreview?
- Using version control with AppleScripts
- AppleScripts aren’t stored as text, which makes it impossible to track changes in AppleScript files using version control software such as Mercurial or Git.
- Save clipboard text to the current folder
- Use the Finder toolbar to save text on the current clipboard directly to a file in the folder that Finder window is displaying.
- Adding parenthetical asides to photograph titles on macOS
- Use Applescript to append a parenthetical to the titles of all selected photographs in Photos on macOS.
- 17 more pages with the topic AppleScript, and other related pages
More appscript
- Using appscript with Apple Mail to get emails and attachments
- Python and appscript can be used to get email messages out of Apple’s OS X Mail client, and even to get and save attachments.
- 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.
More Django
- Converting an existing Django model to Django-MPTT
- Using a SQL database to mimic a filesystem will, eventually, create bottlenecks when it comes to traversing the filesystem. One solution is modified preordered tree traversal, which saves the tree structure in an easily-used manner inside the model.
- Two search bookmarklets for Django
- Bookmarklets—JavaScript code in a bookmark—can make working with big Django databases much easier.
- Fixing Django’s feed generator without hacking Django
- It looks like it’s going to be a while before the RSS feed generator in Django is going to get fixed, so I looked into subclassing as a way of getting a working guid in my Django RSS feeds.
- ModelForms and FormViews
- This is just a notice because when I did a search, nothing came up. Don’t use ModelForm with FormView, use UpdateView instead.
- Django: fix_ampersands and abbreviations
- The fix_ampersands filter will miss some cases where ampersands need to be replaced.
- 29 more pages with the topic Django, and other related pages