# Migrating Django Migrations to Django 2.x

Django is a Python framework for making web applications, and its impressive in its completeness, flexibility and power for speedy prototyping.

It’s also an impressive project for forward planning, it has a kind of built in “lint” functionality that warns about deprecated code that will be disallowed in future versions.

As a result when Django 2.0 was released I didn’t have to make many changes to my app code base to get it to work successfully. However, today when I tried to update my oldest Django App (started in Django 1.8x) I hit an unexpected snag. The old migrations were sometimes invalid. Curiously I don’t think this problem emerged the last time I tried.

Django uses migrations to move the database schema from one version to the next. Most of the time it’s a wonderful system. In the rare case it goes wrong it can be … tricky. Today’s problem is quite specific, and easier to fix.

Django 2.0 enforces that ForeignKey fields explicitly specify a behaviour to follow on deletion of the object pointed to by the key. In general whether we Cascade the deletion, or set the field to Null, getting the behaviour write can be important, particular on fields where a Null value has a legitimate meaning.

But a bit of a sting in the tail is that an older Django project may have migrations created automatically by Django which don’t obey this. I discovered this today and found I couldn’t proceed with my project unless I went back and modified the old migrations to be 2.0 compliant.

So if this happens to you, here are some suggestions on fixing the problem.

You will know if you have a problem if when you try to run your test server, or indeed replace runserver by check

python3 manage.py runserver

you get an error and output like this

  File "/Users/colin/Development/WAM/WAM/loads/migrations/0024_auto_20160627_1049.py", line 7, in <module>
class Migration(migrations.Migration):
File "/Users/colin/Development/WAM/WAM/loads/migrations/0024_auto_20160627_1049.py", line 100, in Migration
TypeError: __init__() missing 1 required positional argument: 'on_delete'


I would suggest you try runserver whatever you did before as it will continue to try each time you save a file.

Open your code with your favourite editor, and open your models.py file (you may have several depending on your project), and the migration file that’s broken as above.

Looking in your migration file you’ll find the offending line. In this case it’s the last (non trivial) line below.

      migrations.AddField(
model_name='activity',
name='activity_set',
),

To ensure that your migrations will be applied consistently with your final model (well, as long as nobody tries to migrate to an intermediate state) look carefully in the correct model (Activity) in this case, and see what decision you make for deletion there. In my case I want deletion of the ActivitySet to kill all linked Activitiy(s). So replicate the “on_delete” choice from there.

      migrations.AddField(
model_name='activity',
name='activity_set',
),

Each time you save your new migration file the runserver terminal window will re-run the check, hopefully moving on to the next migration that needs to be fixed. Work your way through methodically until your code checks clean. Check into source control, and you’re done.

•
•
•
•
•
•
•
•

# Semi Open Book Exams

A few years ago, I switched one of my first year courses to use what I call a semi-open-book approach.

Open-book exams of course allow students to bring whatever materials they wish into them, but they have the disadvantage that students will often bring in materials that they have not studied in detail, or even at all. In such cases, sifting through materials to help them answer a question could be counter productive.

On the other hand, the real world is now an increasingly “open-book” environment, which huge amounts of information available to those in the workplace which is now almost always Internet connected.

So I decided to look at another approach. Students are allowed to bring in a single, personalised, A4 sheet, on which they can write whatever they wish on both sides. There are a few rules:

• the sheet must be written on “by hand”, that is to say, it cannot be printed to from a computer, or typed;
• the sheet must be “original”, that is to say, it cannot be a photocopy of another sheet (though students may of course copy their original for reference);
• the sheet must be the student’s own work, and they must formally declare as much (with a tick box);
• the sheet must be handed in with the exam paper, although it is not marked.

The purpose of these restrictions are to ensure that each student takes a lead in producing an individual sheet, and to inhibit cottage industries of copied sheets.

In terms of what can go on the sheet? Well anything really. It can be sections from notes, important formulae, sample questions or solutions. The main purpose here is to prompt students to work out what they would individually distill down to an A4 page. So they go through all the module notes, tutorial problems and more, and work out the most valuable material that deserves to go on one A4 page. I believe that this process itself is the greatest value of the sheet, its production rather than its existence in the exam. I’m working on some research to test this.

So I email them each an A4 PDF, which they can print out at home, and on whatever colour paper they may desire. The sheet is individual and has their student number on it with a barcode, for automated processing and analysis afterwards for a project I’m working on, but this is anonymised. The student’s name in particular does not appear, since in Ulster University, it does not appear on the exam booklet.

The top of my sheet looks like this:

So, if you would like to do the same, I am enclosing the Python script, and LaTeX that I use to achieve this. You could of course use any other technology, or not individualise the sheet at all.

For convenience the most recent code will also be placed on a GitHub repository here, feel free to clone away.

My script has just been rewritten for Python 3.x, and I’ve added a lot of command line parameters to decouple it from me and Ulster University only use. It opens a CSV file from my University which contains student id numbers, student names, and emails in specific columns. These are the default for the script but can be changed. For each student it uses LaTeX to generate the page. It actually creates inserts for each student of the name and student number, you can then edit open-book.tex to allow the page to be as you wish it. You don’t need to know much LaTeX to achieve this, but ping me if you need help. I am also using a LaTeX package to create the barcodes automatically.

I’ve spent a bit of time adding command line parameters to this script, but you can try using

python3 open-book.py --help

for information. The script has been rewritten for Python 3. If you run it without parameters it will enter interactive mode and prompt you.

I’d strongly recommend running with the –test-only option at first to make sure all looks good, and opening open-book.pdf will show you the last generated page so you can see it’s what you want.

Anyway, feel free to do your own thing, or mutilate the code. Enjoy!

#!/usr/bin/env python

#
#
# Free and Open Source Software under GPL v3
#
import argparse

import csv
import re
import subprocess
import smtplib
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

def process_student(args, row):
"""Takes a line from the CSV file, you will likely need to edit aspects of this."""
'kicks of the processing of a single student'
student_number = row[args.student_id_column]
student_name = row[args.student_name_column]
student_email = row[args.student_email_column]
print('  Processing:', student_name , ':', student_email)
create_latex_inserts(student_number, student_name)
create_pdf()
send_email(args, student_name, student_email)

def create_latex_inserts(student_number, student_name):
"""Write LaTeX inserts for the barcode and student name

For each student this will create two tiny LaTeX files:

* open-book-insert-barcode.tex which contains the LaTeX code for a barcode representing the student number
* open-book-insert-name.tex which will contain simply the student's name

These files can be included/inputted from open-book.tex as desired to personalise that document

student_number is the ID in the students record system for the student
student_name is the name of the student"""

# Open a tiny LaTeX file to put this in
file = open('open-book-insert-barcode.tex', 'w')

# All the file contains is LaTeX to code to create the bar code
string = '\psbarcode{' + student_number + '}{includetext height=0.25}{code39}'
file.write(string)
file.close()

# The same exercise for the second file to contain the student name
file = open('open-book-insert-name.tex', 'w')
string = student_name
file.write(string)
file.close()

def create_pdf():
"""Calls LaTeX and dvipdf to create the personalised PDF with inserts from create_latex_inserts()"""

# Suppress stdout, but we leave stderr enabled.
subprocess.call("latex open-book", stdout=subprocess.DEVNULL, shell=True)
subprocess.call("dvipdf open-book", stdout=subprocess.DEVNULL, shell=True)

def send_email(args, student_name, student_email):
"""Emails a single student with the generated PDF."""
#TODO: Might be useful to improve the to address
#TODO: Allow subject to be tailored.

subject = args.email_subject
# to_address = student_name + ' <' + student_email + '>'

msg = MIMEMultipart()
msg['Subject'] = subject

text = 'Dear Student\nPlease find enclosed your guide sheet template for the exam. Read the following email carefully.\n'
part1 = MIMEText(text, 'plain')
msg.attach(part1)

# Open the files in binary mode.  Let the MIMEImage class automatically
# guess the specific image type.
fp = open('open-book.pdf', 'rb')
fp.close()

msg.attach(img)

# Send the email via our own SMTP server, if we are not testing.
if not args.test_only:
s = smtplib.SMTP(args.smtp_server)
s.quit()

def override_arguments(args):
"""If necessary, prompt for arguments and override them

Takes, as input, args from an ArgumentParser and returns the same after processing or overrides.
"""

# If the user enabled batch mode, we disable interactive mode
if args.batch_mode:
args.interactive_mode = False

if args.interactive_mode:
override = input("CSV filename? default=[{}] :".format(args.input_file))
if len(override):
args.input_file = override

override = input("Student ID Column? default=[{}] :".format(args.student_id_column))
if len(override):
args.student_id_column = int(override)

override = input("Student Name Column? default=[{}] :".format(args.student_name_column))
if len(override):
args.student_name_column = int(override)

override = input("Student Email Column? default=[{}] :".format(args.student_email_column))
if len(override):
args.student_email_column = int(override)

override = input("Student ID Regular Expression? default=[{}] :".format(args.student_id_regexp))
if len(override):
args.student_id_regexp = override

override = input("SMTP Server? default=[{}] :".format(args.smtp_server))
if len(override):
args.smtp_server = override

override = input("Email subject? default=[{}] :".format(args.email_subject))
if len(override):
args.email_subject = override

override = input("Email sender address? default=[{}] :".format(args.email_sender))
if len(override):
args.email_sender = override

return(args)

def parse_arguments():
"""Get all the command line arguments for the file and return the args from an ArgumentParser"""

parser = argparse.ArgumentParser(
description="A script to email students study pages for a semi-open book exam",
epilog="Note that column count arguments start from zero."

)

action='store_true',
dest='batch_mode',
default=False,
help='run automatically with values given')

action='store_true',
dest='interactive_mode',
default=True,
help='prompt the user for details (default)')

dest='input_file',
default='students.csv',
help='the name of the input CSV file with one row per student')

dest='student_id_column',
default=1,
help='the column containing the student id (default 1)')

dest='student_name_column',
default=2,
help='the column containing the student name (default 2)')

dest='student_email_column',
default=9,
help='the column containing the student email (default 9)')

dest='student_id_regexp',
default='B[0-9]+',
help='a regular expression for valid student IDs (default B[0-9]+)')

dest='smtp_server',
default='localhost',
help='the address of an smtp server')

dest='email_subject',
help='the subject of emails that are sent')

dest='email_sender',
help='the sender address from which to send emails')

action='store_true',
dest='test_only',
default=False,
help='do not send any emails')

args = parser.parse_args()

# Allow for any overrides from program logic or interaction with the user
args = override_arguments(args)
return(args)

def main():
"""the main function that kicks everything else off"""

print("Hello")
args = parse_arguments()

print("Starting open-book...")
print(args)

student_count = 0
# Go through each row
student_number = row[args.student_id_column]
# Check if the second cell looks like a student number
if re.match(args.student_id_regexp, row[args.student_id_column]):
student_count = student_count + 1
process_student(args, row)
else:
print('  Skipping: non matching row')

print('Stopping open-book...')

if __name__ == '__main__':
main()


I use a LaTeX template for the base information, this can be easily edited for taste.

\documentclass[12pt,a4paper]{minimal}
\usepackage[latin1]{inputenc}
\usepackage{pst-barcode}
\usepackage[margin=2cm]{geometry}

%
% Does it all have to be Arial now? <sigh>
%
\renewcommand{\familydefault}{\sfdefault}

\author{Professor Colin Turner}
\begin{document}
\begin{centering}
\textbf{EEE122 Examination Guide Sheet}

This sheet, and its contents that you have added, can be brought into
the examination for EEE122. The contents \textbf{must} be compiled
by yourself, be handwritten, and be original (i.e. \textbf{NOT}
photocopied or similar). You may use the
reverse side. You may retain a copy you have made before the examination
but the original must be handed in with your examination scripts at the

%\input{open-book-insert-name.tex}
% D'Oh! Not supposed to put a name on anything going in the exam.
\input{open-book-insert-barcode.tex}
\hfill
\begin{pspicture}(7,1in)
%\psbarcode{
%\input{./open_book_insert.tex}
%B00526636
%}{includetext height=0.25}{code39}
\input{open-book-insert-barcode.tex}
\end{pspicture}
\end{centering}

\vfill
\begin{centering}
Please read the following declaration and tick the box to indicate you agree:

I declare this sheet to have been compiled by myself and not by another, and that the student number above is mine.

%\rule[-1 cm]{10 cm}{1 pt}
\framebox[0.3 cm]{ }

\end{centering}

\end{document}

•
•
•
•
•
•
•
•

# The Most Dangerous Idea in History

In the modern world we often throw around the word meme to mean some comic image, video or idea that has become associated with a concept, but the word has a different origin.

“an element of a culture or system of behaviour passed from one individual to another by imitation or other non-genetic means.”

This usage was coined by Richard Dawkins in his 1976 book “The selfish gene“. Like genes, memes are replicated by one process or another, sometimes with mutations. Like genes, memes are subject to a form of “evolutionary pressure”, a survival of the fittest.

So memes are not just ideas, but ideas can be seen as memes. I’ll likely use the words a bit interchangeably for convenience, however, in this article. The best ideas or memes, can survive for centuries or millennia, as Dawkins himself noted:

“But if you contribute to the world’s culture, if you have a good idea…it may live on, intact, long after your genes have dissolved in the common pool. Socrates may or may not have a gene or two alive in the world today, as G.C. Williams has remarked, but who cares? The meme-complexes of Socrates, Leonardo, Copernicus and Marconi are still going strong.”

In effect, memes can be more immortal and long-lasting then genes. And the transmission can be more direct as well. You may not descend from Socrates, Newton or Curie, whatever benefit that may or may not give you, but you can easily open a book and have those memes transmitted to you directly (or more likely through one or two intermediaries) very efficiently.

This is a very important feature of humanity, perhaps its most important: the ability of a human to learn from more than just its immediate family or peer group, however valuable that interaction is.

Some people have explored the viral nature of memes, and in this sense, we can easily understand that in terms of the common usage of a word in social media.

Of course, some memes are millennia old, have virally spread and are just plain wrong. The popular meme that humans have five senses is wrong. So memes can survive selection pressure despite error. Of course, the pressure may be more intense and effective if the consequences are more significant.

I contend that the most dangerous idea in history is one of a family of related ideas on this theme:

It is bad / wrong / sinful / wicked to question / doubt.

This is a very widespread meme indeed, and a successful one therefore in terms of its own survival. It exists in various strengths and in various contexts. And it doesn’t seem especially dangerous; it’s an innocuous statement.

So what’s the problem? Well, there are two aspects to this.

Firstly, it knocks out your mental immune system. This idea is almost parasitic because it reinforces itself with circular logic. Once it is in place, it prevents or inhibits its own eviction. After all, one has to challenge the idea to reject it, and the mind the idea resides in has already accepted that this is unacceptable.

People that have been taught this as part of their philosophy, ethics or morality, will of course tend to pass it on as a necessary element in those systems, and one can see why.

Because the second aspect is that, this idea rarely comes on its own. The really big problem with this idea is that explicitly or implicitly it tends to actually be found in this form:

It is bad / wrong / sinful / wicked to question / doubt [X].

And then X is or can be the problem. In other words, this is a mental virus that often comes with an associated payload. Like a two-part drug.

For convenience, let’s call X the “payload“, the idea or collection of ideas that hitches a ride with the “immunosuppressant“, the idea that one must not question the payload.

Maybe the payload is trivial, like somebody learning a martial art who has essentially been told not to question anything in what they are being taught. In such cases the immunosuppressant challenge could cause poor form or technique never to really be corrected, or not to be open to improvement from ideas from others.

Surprisingly one can find the immunosuppressant quite easily in class rooms, where groups of students have been told that they have to accomplish a task by certain means are exhorted not to think about why. Or sometimes they are so frightened out of asking questions that they pick up the immunosuppressant meme all by themselves. This can damage their ability to discern good ideas from bad.

If the payload is something more serious, such as having significant ethical or moral content then it might still be a relatively minor problem. For example if the payload is ethically benign such as some variation on the Golden Rule, then few issues arise, since the immunosuppressant defeating aspect is reinforcing a behaviour (payload) that is ethically non damaging or even perhaps, life enhancing.

But, if the payload contains many ethically or morally dubious aspects, then you have real problems, because these ideas and behaviours simply cannot be challenged from outside that mind. If the person swallowing the two part pill has accepted the immunosuppressant wholeheartedly then almost nothing can be done to recover that mind’s proper function. It’s trivially easy to see this at work in the world, where people of a given faith can’t even accept that adherents of different strands of that faith are worthy of respect, or in extreme cases, life itself.

In most cases the payload is complex, comprising both good and bad ideas; in these cases the immunosuppressant is the main reason preventing people from discerning which bits to hang on to and which bits to discard. Fortunately, for many the immunosuppressant isn’t full strength, and they quietly, and quite sensibly work out which parts of the payload to discard, but often with no fanfare. They are sometimes still ashamed to state that they do this or don’t even admit it to themselves.

But we shouldn’t be embarrassed to say that parts of a payload are good and parts should be rejected. For instance, most people of faith, from the Abrahamic tradition, quietly reject parts of the payload, let’s take this one:

“If a man has sexual relations with a man as one does with a woman, both of them have done what is detestable. They are to be put to death; their blood will be on their own heads.”

I mean, there’s no getting around it. It’s perfectly clear, in the payload, and it’s equally clear to most 21st century people that this is wrong. Wrong. Illegal. Murder. Ludicrous even. But still many lovely and kind people will try and apologise for this, quoting nicer parts of the payload, rather than just admitting that this is wrong, often because the immunosuppressant part of the pill says we have to not question any aspects of the payload.

And then we are surprised when people kill each other around the world based on the differences in their ideas, even if those differences are trivial, and pose absolutely no threat whatsoever.

But why should we be surprised? The answer is all too obvious.

Horrifically, in many cases, they have been explicitly told to do these things. It’s there in writing. And the immunosuppressant is strongly in place. It’s no good saying that the payload has lots of nice bits in it too. That’s great. That’s wonderful, but the payload will only become better when people are able to admit that parts of it are just plain wrong, and need to be rejected. For this to happen, the immunosuppressant has to be removed. At this point their natural mental immune response comes back to life. It is then possible for peers to influence people for the best. It is easier and possible to learn from the positive examples of others.

If we want to rid ourselves of some of the worst most horrific memes of our past, we need to admit that this is a possibility, and this is why doubt and questioning isn’t a sin, or an error, but the most basic principle of mental hygiene.

•
•
•
•
•
•
•
•

# Pretty Printing C++ Archives from Emails

I’m just putting this here because I nearly managed to lose it. This is a part of a pretty unvarnished BASH script for a very specific purpose, taking an email file containing a ZIP of submitted C++ code from students. This script produces pretty printed PDFs of the source files named after each author to facilitate marking and annotation. It’s not a thing of beauty. I think I’ll probably write a new cleaner version in future.

#!/bin/bash
#
# A script to take C++ files in coursework and produce pretty printed PDF
# listings named with the author information.
#
# It takes a ZIP file of .cpp and .h files and produces a ZIP file of PDFs
#

# Requires
#   enscript
#   ps2pdf
#   munpack

#
# Called for each file to be encoded
#
pretty_print_file()
{
# Extract the Author JavaDoc information
author=1 | sed -n -e 's/^.*@[Aa]uthor (cat /\1/gp')

# How many lines did we get back?
lines=author_snip" | wc -l)

# If we got no author info
if [ author_snip" ]
then
author="no-author"
author_snip="no-author"
fi

# if we got too many
if [ 1, Author author_snip)"
output=1
output+=".pdf"
echo "Encoding 1 | ps2pdf - parsed-output/1 ]
then
echo "Usage: unpack_coursework <email_file>"
exit
fi

# Make a temporary directory and copy the email file into it.
echo "Creating temporary directory..."
dir=mktemp -d
echo 1 dir

# Unpack the email
echo "Unpacking email..."
munpack f *.cpp
unzip -Cj f
done

# And the same for source files
echo "Parse .cpp files..."
for f in *.cpp
do
pretty_print_file dir/parsed-output
zip parsed-output *.pdf
cd ..

# Back to the directory we started in.
popd

# Copy the parsed ZIP to the current directory for inspection and marking
cp dir
rm -rf \$dir


•
•
•
•
•
•
•
•

# Installing Android Nougat on a Stock Galaxy Tab 10.1

My daughter uses an Android Samsung tablet (coded GT-P7510) which ended official support on Android 4.0.4. Unfortunately I didn’t pay any attention to this issue until the apps she most wanted to use, namely Netflix and YouTube stopped working on it as the Android version was too low.

I found a ROM to upgrade to Android 7.1 (Nougat) with some cost – for instance, the camera doesn’t work, but Aimee doesn’t care about that. So I decided to try upgrading it since the tablet was otherwise now utterly useless.

To make things more difficult, most of the information on upgrading this tablet on the Internet is outdated or wrong, or pre-supposes that the device was long since updated. I also don’t run Windows, and ran into some problems with the Heimdall alternative.

So this quick article is the result of a couple of evenings running into dead ends. It might help someone else. Certainly if I ever need to do it again it’ll help me.

But as usual, if you break something, you own all the parts. These instructions are completely specific to this particular tablet, and the wifi only version at that. Make sure your device is fully charged before you start.

A new recovery image had to be installed first, and some steps had to be undertaken just to get that far.

## Heimdall

First of all there’s supposed to Windows software called Odin that is used to update the ROM, especially from a stock start. I can’t run that without emulation since I don’t run Windows, and in any case, I suspect it might behave badly in a virtual machine, and probably wouldn’t run correctly on modern Windows.

So I installed a Free and Open Source alternative known as Heimdall. For me, this was simple as it was Debian packaged. I couldn’t get the frontend to be useful, and I couldn’t get the Java version of the frontend to work online or offline. So I defaulted to the command line.

So, as root on Debian GNU/Linux:

aptitude install adb heimdall

This is also ensuring all the command line tools for android debugging are installed (I already had these).

## Receiving TWRP

The device needs to be made ready for Odin / Heimdall upload. Turn the device off, and then hold Power and Volume Down till it appears with two icon choices. You want the one on the right. Use Volume Up to select, and use Volume Up again to bypass the dire warnings.

I had no success in using the Heimdall frontend, your mileage may vary. I got the correct archive for my purposes from here.

I downloaded the archive, and used tar xvf to extract the contents. You will find two .img files, recovery.img and hidden.img. You’ll need both.

Note that the partition target on the device for recovery is not called recovery but is called SOS at least on my device.

heimdall flash --SOS recovery.img --no-reboot

Because of the no-reboot option note that the tablet will continue to warn you not to restart it. You’ll need to watch the command line progress carefully to ensure that it is on. Now reboot the machine once again into the Odin / Heimdall mode again. I.e. power it off, and turn it on with Power and Volume Down.

Now flash

heimdall flash --HID hidden.img

For me this successfully got TWRP 3.0.3 loaded. It was a major odyssey of conflicting information to get this far. When you reboot make sure you hold down volume down to get to the recover menu, (and now choose the left hand option). If you don’t do this, the stock ROM overwrites the new one and you’ll need to start again.

## Using TWRP

From here, things were relatively plain sailing. I got the ROM from here. Incidentally, I’d tried other recovery ROMs I got onto the device before when I couldn’t get TWRP onto it, they did not allow the following steps to work.

I then used TWRP’s wipe option to wipe Cache, Data, and Dalvik Cache.

I used the Advanced button and put the device into sideload mode.

I then, from the Linux command prompt executed

adb sideload aosp-7.1-p4wifi-20170320.zip

I then did not reboot but went back in TWRP and selected sideload again, this time I was careful to uncheck the wipe data and cache items since I’m loading other items on top of the basic ROM image.

adb sideload P7500-open_gapps-arm-7.1-pico-20170119.zip

and I repeated the same for the last package

adb sideload superuser.zip

finally I selected to reboot the tablet. It took a pretty long time to boot. Don’t forget it’s a relatively underpowered device.

The device is up and running and now runs the apps my daughter wants again.

• 2
•
•
•
•
•
•
•

# The Deceptiveness of Coincidence

A friend of mine recently posted about a chain of events – people sharing birthdays – that was so unlikely that a lottery ticket purchase was called for. Most people might make similar comments as the oddity of these events struck them. There followed some discussion about these problems and it made me think of trying to set out the issues more carefully.

I’ve done a lecture, in some variations (for school audiences), almost every year about the deceptiveness of common sense in mathematics. Indeed, it provided the title of this blog.

## The Birthday Problem

One of the problems I examined is the famous birthday problem. Let’s look at two aspects of this.

How many people must be in a room before we are certain that two of them share the same birthday?

This can be answered by using the so called pigeon hole principle. In this case, the pigeon holes are all the days of the calendar in a year, 365, or let’s say 366 to allow for the worst case scenario of a leap year. The people in the room are the pigeons. You can place 366 pigeons into the holes and keep them all separate, but any more ensures that at least two must share one of the holes. So, similarly, you need 367 people to be absolutely sure that one pair in the room shares a birthday.

Let’s look at a more subtle question.

How many people must be in a room so that it is more likely than not that two of them share the same birthday?

It turns out this number is deceptively low. We reach a 50% chance (more likely than not) with just 23 people in the room. Perhaps even more surprisingly we reach a 99.9% chance with 70 people in the room. The calculations for the probabilities are set out in the link to the lecture, and the wikipedia article as mentioned above, but I don’t want to focus on that. I want to focus on why the result is so shocking to most people.

The issue is the collision of relatively rare events (a birthday) with numerous combinations. For instance if you want to consider how many pairs one can form in the room we can see this.

If there are three people in the room, A, B and C, there are three possible pairs: AB, AC, and BC. If there are four people in the room, A, B, C and D, there are six possible pairs: AB, AC, AD, BC, BD, and CD. There’s actually a nice formula for this and you can work this out for more examples.

You don’t need to know the formula, but if you want it is

People in Room Possible Pairs
3 3
4 6
10 45
15 105
20 190
23 253
30 435
50 1225
70 2415

You can see just how rapidly the number of pairs increases. It might seem un-obvious that 23 people is as likely as not to produce a duplicate birthday, but it’s perhaps less so when you realise that we are not predicting which pigeon hole has two pigeons in it to go back to our analogy. But given 253 possible pairings, it seems less unlikely even intuitively that there will be “collisions” within one of those pairs.

## Coin Tosses

Let me talk about a similar problem, flipping a (fair) coin to get a Head (H) or a Tail (T). Let’s consider that we flip the coin three times. The possible outcomes are:

HHH, HHT, HTH, THH, TTH, THT, TTH, TTT.

These are all absolutely equally likely, and even this is sometime unintuitive. The coin has no memory of how it performed, and no desire to conform to some concept such as a “law of averages“. But we tend to categorise these 8 possible events into 4 categories.

• Three Heads: (one way this can happen, so 1/8 probability)
• Two Heads, One Tail: (three ways this can happen, so 3/8 probability)
• One Head, Two Tails: (three ways this can happen, so 3/8 probability)
• Three Tails: (one way this can happen so 1/8 probability).

In doing so, we can see why the HHH and TTT events look particularly exciting and rare. They are no more rare than any other individual outcome, but it’s the groupings of how one can get two heads that makes that collection of events more unexciting.

To take this to more of an extreme. If we flip a coin ten times, then these outcomes are all exactly equally likely.

1. HHHHHHHHHH
2. HTHTHTHTHT
3. HHTHTTHTTH
4. THTTHHHTHT
5. TTTTTTTTTT

But some will be far more remarked upon that others. But even if we have an experiment that flips a coin 10 times over and over again enough times, we will expect to see all of these (more or less, eventually) equally often, but we will probably not consider these exciting except maybe outcomes 1,5 and maybe 2.

## Genealogy

Another example, closer to home. Let’s assume that there have been 7,500 generations of Homo Sapiens since its emergence as a species.

The probability that in each of those 7,500 generations in both your paternal and maternal line that all the pairings took place between the right people at the right time in the right circumstances to give rise to you is astronomical. You have two parents, four grand parents, 8 great grand parents and so on. Just for that collection of people. If we add you into the mix, then the number of pairings involved over these 7500 generations is

That number has 2259 digits, you can see them here on Wolfram Alpha.

That’s the number of people involved to get to you, including you. They all had to survive and meet at the right time, and um, do everything else at exactly the right time in the right way. What are the odds? And yet you are here… You might find that exceptional, and of course in a way it is, but the point is, you are not more likely or unlikely than most other human beings that have been born, or countless potential humans who might have been but were not born because their parents never even met. The fact that you specifically exist is unlikely, but the fact that if you did not someone else would exist instead is highly likely and predictable.

Naturally, however, we tend to think of our own existence rather more than some random person on the other side of the planet. But each one of us is an example of the coin flipping perfectly as needed every single time. Sometimes a Head, sometimes a Tail, but that exact perfect sequence essential to produce you. Naturally we are each quite taken with the outcome that produces us and also our nearest and dearest. But if it had not happened, we wouldn’t be here to wonder about it.

## Availability and Psychology

As you can see there is more to this than mathematics and probability, there is also perception and psychology, specifically a concept called the Availability Heuristic. Some things stand out to us as being significant when they are no more significant than other events we ignore.

You might live in a city of a million people. You might personally know only a hundred. You might pass a thousand on your commute to work each day. You do that hundreds of times a year. The number of possible combinations of people you will see over a year is enormous, but you won’t remember it as a special event when you bump into all the people who aren’t dear to you.

## The Anthropic Principle

A nice example of this thinking is the question that many people have asked which is essentially: why is the Universe, and our Earth, set up so nicely to allow humans to exist? If certain physical constants were just a bit different then matter as we know it couldn’t form. If we were just a bit closer to the Sun we couldn’t survive and so on.

These things are all true. But in a Universe with inappropriate physical constants there will be no matter by definition, and certainly no sentient matter like us to notice.

If the Earth was closer to the Sun, homo sapiens would not exist as it currently does, but like as not some other species would, and it would also think how perfectly the world was formed for them. This concept, that we tend to think the Universe is so special to have created us, despite the fact that if not we wouldn’t be here to think about it, is often called the Anthropic Principle.

We believe that the improbability of life itself or us as a species is an example of the collision between billions of individually improbable events with the billions of events than could have occurred, and the billions of years in which they might have done so.

We end up having to think about such things with great care because our evolution on the African savannah didn’t really equip us to consider them. So we shouldn’t be surprised that we are surprised, but we still are.

It’s the old cliché: you are unique and special; just like everyone else.

There are always dangers in imagining our special place as a species.

“This is rather as if you imagine a puddle waking up one morning and thinking, ‘This is an interesting world I find myself in — an interesting hole I find myself in — fits me rather neatly, doesn’t it? In fact it fits me staggeringly well, must have been made to have me in it!’ This is such a powerful idea that as the sun rises in the sky and the air heats up and as, gradually, the puddle gets smaller and smaller, frantically hanging on to the notion that everything’s going to be alright, because this world was meant to have him in it, was built to have him in it; so the moment he disappears catches him rather by surprise. I think this may be something we need to be on the watch out for.”

•
•
•
•
•
•
•
•

# Python Script To Copy a Playlist and Linked Files

Most tools for copying files onto MP3 players (often actually a phone these days) work on the basis that you copy your entire music catalogue and then make playlists linking to various files on it.

It can be a trickier process to copy your favourite selected audio files – the ones specifically used in your playlist, that usually exist in a deep structure of their own. There seem to be some proprietary tools for it, but here’s a Free (in all senses) and Open Source solution.

I adapted my previous Python scripts (that allow for files to be appended to a playlist on the command line, and shuffle a playlist from the command line) to make a – well – bit of a Frankenstein’s monster that will take a playlist as input, and copy the playlist and all its referenced file to another directory. It will create the required directory structure, but will only copy the actual music files on the playlist.

I have used this successfully to move a playlist and the selected directory structure onto more modest sized music players.

Usage looks something like this.

playlist-copy.py -v -i /path/to/your/list.m3u -o /path/where/you/want/it/all.m3u

Have fun, use with care, and I’ll try to clean it up at some stage. If you break something, you own all the pieces, but feel free to write me a (nice) comment below.

At some point I’ll move these scripts to Python 3 and off the deprecated option handling code module.

#!/usr/bin/env python

#
# Simple script to copy a whole playlist and its contents
# Colin Turner <ct@piglets.com>
# 2016
# GPL v2
#
# Need to move all of these scripts to Python 3 in due course

import random
import re
import os
import shutil
import eyeD3

# We want to be able to process some command line options.
from optparse import OptionParser

def process_lines(options, all_lines):
'process the list of all playlist lines into three chunks'
# Eventually we want to support several formats
m3u = True
extm3u = False
if options.verbose:
print "Read %u lines..." % len(all_lines)
middle = list()
footer = list()

# Check first line for #EXTM3U
if re.match("^#EXTM3U", all_lines[loop]):
item.append(all_lines[loop])
loop = loop + 1
# A proper regexp for filenames would be good
if loop < len(all_lines):
item.append(all_lines[loop])
loop = loop + 1
if options.verbose: print item
middle.append(item)

'loads the playlist into an array of arrays'
if options.verbose:
print "Reading playlist %s ..." % options.in_filename
with open(options.in_filename, 'r') as file:
(header, middle, footer) = process_lines(options, all_lines)

'writes the shuffled playlist'
if options.verbose:
print "Writing playlist %s ..." % options.out_filename
with open(options.out_filename, 'w') as file:
file.write(line)
for item in middle:
# Get the filename, as it will be
mp3_filename = resolve_mp3_filename(item[1], options)
copy_mp3(item[1], mp3_filename, options)
for line in item:
# TODO We should wewrite line 1
file.write(line)
for line in footer:
file.write(line)

def copy_mp3(source, destination, options):
'copys an individual mp3 file, making directories as needed'
source_playlist_path = os.path.dirname(os.path.abspath(options.in_filename))

destination_playlist_path = os.path.dirname(os.path.abspath(options.out_filename))

mp3_source = source_playlist_path + os.path.sep + source
mp3_destination = destination_playlist_path + os.path.sep + destination

mp3_destination_dir = os.path.dirname(os.path.abspath(mp3_destination))

if not os.path.exists(mp3_destination_dir.rstrip()):
os.makedirs(mp3_destination_dir.rstrip())

shutil.copyfile(mp3_source.rstrip(), mp3_destination.rstrip())

def copy(options):
'perform the copy on the playlist'
# read the existing data into three arrays in a tuple
# and shuffle the lines array
if options.verbose:
print "Copying..."
# now spit them back out

def append(options, artist, title, seconds):
'append the fetched data to the playlist'
mp3_filename = resolve_mp3_filename(options)
# Check if the playlist file exists
there_is_no_spoon = not os.path.isfile(options.out_filename)

with open(options.out_filename, 'a+') as playlist:
# was the file frshly created?
if there_is_no_spoon:
print >> playlist, "#EXTM3U"
else:
# There was a file, so check the last character, in case there was no \n
playlist.seek(-1, os.SEEK_END)
if(last_char != '\n'):
print >> playlist

# OK, now able to write
print >> playlist, "#EXTINF:%u,%s - %s" % (seconds, artist, title)
print >> playlist, "%s" % mp3_filename

def resolve_mp3_filename(filename, options):
'resolve the mp3 filename appropriately, if we can, and if we are asked to'
'there are three modes, depending on command line parameters:'
'-l we write the filename precisely as on the command list'
'-r specifies a base relative which to write the filename'
'otherwise we try to resolve relative to the directory of the playlist'
'the absolute filename will be the fall back position is resolution is impossible'

if options.leave_filename:
# we have been specifcally told not to resolve the filename
mp3_filename = filename
if options.verbose:
print "Filename resolution disabled."

if not options.leave_filename and not len(options.relative_to):
# Neither argument used, automatcally resolve relative to the playlist
(playlist_path, playlist_name) = os.path.split(os.path.abspath(options.out_filename))
options.relative_to = playlist_path + os.path.sep
if options.verbose:
print "Automatic filename resolution relative to playlist base %s" % options.relative_to

if len(options.relative_to):
# We have been told to map the path relative to another path
mp3_filename = os.path.abspath(filename)
# Check that the root is actually present
if mp3_filename.find(options.relative_to) == 0:
# It is present and at the start of the line
mp3_filename = mp3_filename.replace(options.relative_to, '', 1)

if options.verbose:
print "mp3 filename will be written as %s..." % mp3_filename
return mp3_filename

def get_meta_data(options):
'perform the append on the playlist'
# read the existing data into three arrays in a tuple
if options.verbose:
print "Opening MP3 file %s ..." % options.in_filename
if eyeD3.isMp3File(options.in_filename):
# Ok, so it's an mp3
audioFile = eyeD3.Mp3AudioFile(options.in_filename)
tag = audioFile.getTag()
artist = tag.getArtist()
title = tag.getTitle()
seconds = audioFile.getPlayTime()
if not options.quiet:
print "%s - %s (%s s)" % (artist, title, seconds)
# OK, we have the required information, now time to write to the playlist
return artist, title, seconds
else:
print "Not a valid mp3 file."
exit(1)

def print_banner():
print "playlist-copy"

def main():
'the main function that kicks everything else off'

usage = "usage: %prog [options] arg"
parser = OptionParser(usage)
help="write new playlist to FILENAME")
help="leaves the mp3 path as specified on the command line, rather than resolving it")
help="resolves mp3 filename relative to this path")
action="store_true", dest="verbose")
action="store_true", dest="quiet")

(options, args) = parser.parse_args()
#  if len(args) == 0:
#      parser.error("use -h for more help")

if not options.quiet:
print_banner()

copy(options)

if not options.quiet:
print "Playlist copy complete..."

if  __name__ == '__main__':
main()



• 4
•
•
•
•
•
•
•

# Resistance in Aikido

If you spend a little bit of time on-line looking at what other martial arts practitioners have to say about aikido, one of the thing you note is that people with little or no experience whatsoever about aikido still have plenty to say about it.

The most common comments is that aikido has no sparring and doesn’t train against opponents offering resistance.

Let me first take the latter point. What others assert here is that in aikido dojos people don’t train to execute a technique against someone actively resisting it.

The is a little bit true, a little bit false, and a whole lot completely missing the point.

What people see as training against no resistance is the stage when beginners are learning a technique and need to understand its shape. At this stage the uke, the attacker, who ideally will be a more senior student will indeed sometimes help the other person complete the technique.

This is not dissimilar to early training in other martial arts where people punch thin air, or cut it with bokken.

However, as the student progresses you can expect the uke to be much less of a push over, literally and metaphorically – at least in most dojos. People don’t just fall over when the technique is wrong, there may indeed be some resistance. There will be a touch here and there that could have been a counter strike. How much of this resistance there should be is a point of real controversy. It is possible to find dojos who think it should always be none, and others who think it should be immense and unrelenting.

In the main there is a recognition that it does not demonstrate skill to prevent a technique that you know is coming, and so it’s poor form to do so for its own sake when people are first learning a technique.

For some people this demonstrates the inefficacy of aikido, but it’s no different than suggesting that a person in another art, knowing that a kick to the head is coming, can easily avoid or counter that.

## The Way of Harmony

But the main point here is what ai ki do means – the way of harmony with ki of your attacker, or the energy of your attacker. In other words, when an opponent resists, they resist a specific thing. The whole point of aikido is not to force your technique against your opponent’s energy. Yes, you may be able to force it on. But that is not aikido. There are also tricks of the trade that can make a technique work even when it’s probably stupid to apply it, but these should be reserved for moments when you find yourself in the equivalent of having your heels on a cliff edge with tigers below.

So what should an aikidoka do, when they begin technique A and their opponent resists it? Pretty much anything other than A. When you watch someone do aikido badly, you can see this tension all the time, the nage, the defender, has a plan to do technique A. The painful bit is that if A is not possible, the longer it takes nage to work this out, and move on, the movement between attacker and defender jams up, it is actually a point of real vulnerability, not strength for the nage.

So aikido is about learning that the instant A becomes untenable, you let go of it, and move to B, C, and so on. The more someone resists a specific thing, the more vulnerable they become to its opposite.

So coming back to the lack of “sparring”. This is actually also untrue, this kind of practice does occur in many dojos – it’s usually a different kind of sparring – and it’s valuable if controlled, but real aikido happens all the time when the initial technique goes wrong, the challenge is to realise that trying to push through resistance is completely the wrong approach.

• 7
•
•
•
•
•
•
•

# OPUS and Assessment 3 – Regime Change

This is the third and final article in a short series on how OPUS, a system for managing placement on-line, handles assessment. You probably want to read the first and second article before getting into this.

# Regime Change

It’s not just in geo-political diplomacy that regime change is a risky proposition. In general you should not change a regime once it has been established and students entered on to it. If you do, there is a risk that marks and feedback will become unavailable for existing assessments, or that marks are calculated incorrectly and so on. Obviously it is also non-ideal practice for the transparency of assessment.

Instead you should create a new regime in advance of a new academic year, change the assessment settings in the relevant programmes of study to indicate that regime will come into force in the new year, and brief all parties appropriately. All of this is done by the techniques covered in the first two articles. If you have done all that, well done, and you can stop reading now.

# TL;DR DON’T DO THIS, TURN BACK NOW!

This shouldn’t ever happen, as noted really you need to ensure your regime changes are correctly configured and enabled before any students start collecting marks.

And yet, it does happen, or at least it has happened to me twice that I have been asked to make tweaks to a regime where student marks already exist. Indeed it is happened to me this week, hence this article.

Even changing small details like titles will effect the displayed data for students from previous years. Tweaking weightings could cause similar or more serious problems.

So what happens if we create a new regime and move our students onto it midstream? Well, the existing marks and feedback are recorded against the old regime, so they will “disappear” unless and until the students are placed back on that regime.

If you want to do this, and copy over the marks from the old regime into the new regime, there is a potential way to do this. It is only been used a handful of times and should be considered dangerous. It also probably won’t work if your original marks use a regime where the same assessment appears more than once in the regime for any given student.

But, if you’re here and want to proceed, it will probably be possible using what was deliberately undocumented functionality.

You will need command line, root access (deliberately – this is not a bug), in order to do this. If you haven’t got root access you need to get someone who does so you can… Read all the instructions before starting.

## 0. BACK UP ALL YOUR DATA NOW

Before contemplating this insanity, ensure your OPUS database is backed up appropriately. I’d also extract a broadsheet of all existing collected assessment for good measure from the Information, Reports section of the Admin interface.

That said, this functionality deliberately copies data, it doesn’t delete it – but still.

## 0. NO REALLY, BACK UP ALL YOUR DATA NOW, I REALLY MEAN IT.

Ok, you’re still here.

First of all this approach only makes sense (obviously) if the marks you have already captured are valid. I.e. the assessment(s) you want to change are in the future for the students and haven’t been recorded. If not, then obviously OPUS can’t help you do anything meaningful with the marks you have already collected.

## 1. Make your New Assessment(s)

Maybe you plan to just change from one stock assessment to another, or perhaps you want to adjust a weighting on an existing assessment that hasn’t been undertaken by students in this year. In this case, you can skip this step.

But if needed, create and test any new assessments following the approach laid out in the second article in this series. Do make sure you spend some time testing the form.

## 2. Add and Configure a New Assessment Regime

Create your new assessment regime, as detailed in the first article, but don’t link it to any programmes yet.

Your new regime should be configured as you wish it to be. Remember, for there to be any point in this exercise, the early assessments already undertaken by the students need to be the same (though not necessarily in the same order) – otherwise OPUS can’t help and you need to sort out all the marks in transition entirely manually.

## 3. Note the IDs of the Old and New Regimes

Things start to get clunky at this point. Remember, we are heading off road. You will need the database ID of both the old regime and the new one.

You can obtain these by, for instance, going to Assessment Groups in the Configuration menu and editing the regimes in turn. The URL will show something like this:

At the very end, you will “id=2” so 2 is the id we want. Write these down for both regimes, noting carefully the old and new one. It’s almost certain the new id will be larger than the old one.

## 4. Choose your timing well

You want to complete the steps from here on in, smoothly, in a relatively short time period. It is advisable that you switch OPUS into maintenance mode in a scheduled way with prior warning. This can be done from the Superuser, Services menu in the admin interface, if you are a superuser level admin – if you aren’t you shouldn’t be doing this without the help of such a user. You can also enter maintenance mode with the command line tool.

## 5. Use the Command Line Tool with root access

OPUS ships with a command line utility. With luck, typing “opus” from a root command prompt will reveal it. It’s usually installed in /usr/sbin/ and may not require root access in general, but it most certainly will insist on it for this use.

If that didn’t work, go find it in the cron directory of your OPUS install and run it with

php opus.php

If you needed this to work, you’ll need to use instead of just using “opus” in the next command. We need a command called copy_assessment_results and you’ll note it’s not on the list. It’s not on the dev_help list either, because … did I mention this is a stupid thing to do? You need to enter in the command as follows changing the id for old and new regimes to be those you wrote down in step 3. All on one line.

opus copy_assessment_results old_regime_id=1&new_regime_id=2

Don’t run this more than once, the code isn’t smart enough not to copy over an additional set of data with possibly “exciting” results.

This copies assessment results and feedback, and marks from one regime to another. It’s potentially wasteful but it can’t identify the correct students and doesn’t delete data as an obvious precaution.

## 6. Enable the New Regime for Students

Even in maintenance mode, Superuser admins can log in and act. You can switch over your regime now. Maybe do this for one programme and test the results before using the bulk change facility discussed in the previous article.

With luck you will see your shiny new assessment regime with the old marks and feedback for the existing work in the old regime copied over. Older students on the old regime should still show their results and feedback correctly.

If not – well, this is what that backup in step 0 was for, right? And you’ll have to do it manually from the broadsheet you exported as well.

## 7. Re-enable Normal Access

Either from the command line tool with

opus start

or from the Superuser, Services menu, re-open OPUS for formal access.

## 8. Corrective Action

Explain to relevant colleagues the pain and stress of having to do this and that in future all assessment regime changes should be done appropriately, before students begin completing assessments.

•
•
•
•
•
•
•
•

# OPUS and Assessment 2 – Adding Custom Assessments

This is a follow on to the previous article on setting up assessment in OPUS, an on-line system for placement learning. You probably want to read that first. This is much more advanced and requires some technical knowledge (or someone that has that).

## Making New Assessments

Suppose that OPUS doesn’t have the assessment you want, then you will have to build your own, from scratch, or by modifying an existing one. This takes some minor HTML skill and access to your OPUS code to add a new file. So if you can’t do this yourself, ensure you get appropriate support.

Look at an existing assessment closely first. Go back to Advanced on the OPUS admin menu, and then Assessments.

For each assessment, clicking on Structure allows access to underlying variables that are captured. These can be numeric, text, or checkboxes, and some validation is possible too.

you need to work out what things you will capture, and create a skin for the assessment, most usually from modifying one from another. This following snippet from a related Smarty template shows this is just HTML, but OPUS, through Smarty drops in an assessment->flag_error(“mark5″)}
<input type=”text” class=”data_entry_required” size=”2″ value=”{assessment->flag_error(“comment5″)}
{include file=”general/assessment/textarea.tpl” name=”comment5″ rows=”7″ cols=”60″}
</td>
</tr>

<tr><td colspan=”5″>Marking Scheme</td></tr>
<tr>
<td> Uses language to clearly express views concisely </td>
<td> Expresses clearly but with some minor errors </td>
<td> Good expression, logical flow, reasonably concise </td>
<td> Reasonable flow, some contorted expressions, a little verbose </td>
<td> Poor expression, verbose, some colloquialisms </td>
</tr>

This is a representative snippet. You can see this full template here. Note the “special” code in between braces { }. The variables in the template pertain to the names in the structure.

## Create and Save Your Template

Create your template, probably using one of the existing ones to help you understand the format. This provides the layout and skin for your pro-forma and allows you to do anything you can wish with HTML/CSS. Be mindful of security considerations, but you aren’t writing main code, just an included bit. OPUS will top and tail the file for you when it runs.

Save it under the templates/assessments directory in your OPUS install. I recommend you make a subdirectory for your institution.

Avoid using the “uu” directory. This is used for pre-shipped assessments and those used at Ulster University. There is a chance your changes will get clobbered by a new OPUS version if you put your template in there.

## Adding the Assessment variables into OPUS

Then you need to create your new Assessment item itself as at the top of the article. Once you have created it, click on structure and add each variable you will capture in turn, whether it is text, a number, or a checkbox, and any simple validation rules – such as minimum or maximum values.

The description appears in feedback and validation, so make sure it is meaningful to the end user. The name is the variable name as it appears in your template. The weighting field is used to determine if numeric values contribute to the score. Usually use 1 if you want the score to be counted, and 0 if you want the score to be ignored. Finally you can choose whether each field is compulsory or not. Optional fields will be ignored in a total when OPUS creates a percentage.

Once complete, add your new assessment into a test regime as detailed in the first article and do some careful testing before adding the regime to live students.

•
•
•
•
•
•
•
•