+[[!meta title="Edit stages"]]
+[[!meta author="rohieb"]]
+[[!meta license="GNU LGPL, v2.1 or later"]]
+[[!toc]]
+
+This page shows the different edit stages of a 1000-line patch which mostly
+consisted of white space changes in one single hunk, and which was subsequently
+split into smaller hunks. See the [[related blog
+post|../splitting-overly-large-hunks-in-patches]] for an explanation what was
+done.
+
+The code below originates from the [QDjango][] project, which is released under
+the terms of the [GNU Lesser General Public License][gnu-lgpl], version 2.1 or
+later.
+
+[QDjango]: https://code.google.com/p/qdjango/
+[gnu-lgpl]: http://www.gnu.org/licenses/lgpl.html
+
+## The original patch
+
+…with far too much whitespace changes:
+
+[[!format diff """
+--- qdjango-0.2.6.orig/src/db/QDjangoMetaModel.cpp 2012-09-09 07:34:12.000000000 +0200
++++ qdjango-0.2.6/src/db/QDjangoMetaModel.cpp 2012-09-09 08:35:54.000000000 +0200
+@@ -27,583 +27,590 @@
+ class QDjangoMetaFieldPrivate : public QSharedData
+ {
+ public:
+- QDjangoMetaFieldPrivate();
++ QDjangoMetaFieldPrivate();
+
+- bool autoIncrement;
+- QString db_column;
+- QString foreignModel;
+- bool index;
+- int maxLength;
+- QByteArray name;
+- bool null;
+- QVariant::Type type;
+- bool unique;
++ bool autoIncrement;
++ QString db_column;
++ QString foreignModel;
++ bool index;
++ int maxLength;
++ QByteArray name;
++ bool null;
++ QVariant::Type type;
++ bool unique;
+ };
+
+ QDjangoMetaFieldPrivate::QDjangoMetaFieldPrivate()
+- : autoIncrement(false),
+- index(false),
+- maxLength(0),
+- null(false),
+- unique(false)
++ : autoIncrement(false),
++ index(false),
++ maxLength(0),
++ null(false),
++ unique(false)
+ {
+ }
+
+ /*!
+- Constructs a new QDjangoMetaField.
++ Constructs a new QDjangoMetaField.
+ */
+ QDjangoMetaField::QDjangoMetaField()
+ {
+- d = new QDjangoMetaFieldPrivate;
++ d = new QDjangoMetaFieldPrivate;
+ }
+
+ /*!
+- Constructs a copy of \a other.
++ Constructs a copy of \a other.
+ */
+ QDjangoMetaField::QDjangoMetaField(const QDjangoMetaField &other)
+- : d(other.d)
++ : d(other.d)
+ {
+ }
+
+ /*!
+- Destroys the meta field.
++ Destroys the meta field.
+ */
+ QDjangoMetaField::~QDjangoMetaField()
+ {
+ }
+
+ /*!
+- Assigns \a other to this meta field.
++ Assigns \a other to this meta field.
+ */
+ QDjangoMetaField& QDjangoMetaField::operator=(const QDjangoMetaField& other)
+ {
+- d = other.d;
+- return *this;
++ d = other.d;
++ return *this;
+ }
+
+ /*!
+- Returns the database column for this meta field.
++ Returns the database column for this meta field.
+ */
+ QString QDjangoMetaField::column() const
+ {
+- return d->db_column;
++ return d->db_column;
+ }
+
+ /*!
+- Returns true if this is a valid field.
++ Returns true if this is a valid field.
+ */
+ bool QDjangoMetaField::isValid() const
+ {
+- return !d->name.isEmpty();
++ return !d->name.isEmpty();
+ }
+
+ /*!
+- Returns name of this meta field.
++ Returns name of this meta field.
+ */
+ QString QDjangoMetaField::name() const
+ {
+- return QString::fromLatin1(d->name);
++ return QString::fromLatin1(d->name);
+ }
+
+ /*!
+- Transforms the given field value for database storage.
++ Transforms the given field value for database storage.
+ */
+ QVariant QDjangoMetaField::toDatabase(const QVariant &value) const
+ {
+- if (d->type == QVariant::String && !d->null && value.isNull())
+- return QString("");
+- else if (!d->foreignModel.isEmpty() && d->type == QVariant::Int && d->null && !value.toInt()) {
+- // store 0 foreign key as NULL if the field is NULL
+- return QVariant();
+- } else
+- return value;
++ if (d->type == QVariant::String && !d->null && value.isNull())
++ return QString("");
++ else if (!d->foreignModel.isEmpty() && d->type == QVariant::Int && d->null && !value.toInt()) {
++ // store 0 foreign key as NULL if the field is NULL
++ return QVariant();
++ } else
++ return value;
+ }
+
+ static QMap<QString, QString> parseOptions(const char *value)
+ {
+- QMap<QString, QString> options;
+- QStringList items = QString::fromUtf8(value).split(' ');
+- foreach (const QString &item, items) {
+- QStringList assign = item.split('=');
+- if (assign.size() == 2) {
+- options[assign[0].toLower()] = assign[1];
+- } else {
+- qWarning() << "Could not parse option" << item;
+- }
+- }
+- return options;
++ QMap<QString, QString> options;
++ QStringList items = QString::fromUtf8(value).split(' ');
++ foreach (const QString &item, items) {
++ QStringList assign = item.split('=');
++ if (assign.size() == 2) {
++ options[assign[0].toLower()] = assign[1];
++ } else {
++ qWarning() << "Could not parse option" << item;
++ }
++ }
++ return options;
+ }
+
+ class QDjangoMetaModelPrivate : public QSharedData
+ {
+ public:
+- QList<QDjangoMetaField> localFields;
+- QMap<QByteArray, QString> foreignFields;
+- QByteArray primaryKey;
+- QString table;
++ QList<QDjangoMetaField> localFields;
++ QMap<QByteArray, QString> foreignFields;
++ QByteArray primaryKey;
++ QString table;
+ };
+
+ /*!
+- Constructs a new QDjangoMetaModel by inspecting the given \a model instance.
++ Constructs a new QDjangoMetaModel by inspecting the given \a model instance.
+ */
+ QDjangoMetaModel::QDjangoMetaModel(const QObject *model)
+- : d(new QDjangoMetaModelPrivate)
++ : d(new QDjangoMetaModelPrivate)
+ {
+- if (!model)
+- return;
++ if (!model)
++ return;
+
+- const QMetaObject* meta = model->metaObject();
+- d->table = QString(meta->className()).toLower().toLatin1();
++ const QMetaObject* meta = model->metaObject();
++ d->table = QString(meta->className()).toLower().toLatin1();
++
++ // parse table options
++ const int optionsIndex = meta->indexOfClassInfo("__meta__");
++ if (optionsIndex >= 0) {
++ QMap<QString, QString> options = parseOptions(meta->classInfo(optionsIndex).value());
++ QMapIterator<QString, QString> option(options);
++ while (option.hasNext()) {
++ option.next();
++ if (option.key() == "db_table")
++ d->table = option.value();
++ }
++ }
++
++ const int count = meta->propertyCount();
++ for(int i = QObject::staticMetaObject.propertyCount(); i < count; ++i)
++ {
++ QString typeName = meta->property(i).typeName();
++ if (!qstrcmp(meta->property(i).name(), "pk"))
++ continue;
++
++ // parse field options
++ bool autoIncrementOption = false;
++ QString dbColumnOption;
++ bool dbIndexOption = false;
++ bool ignoreFieldOption = false;
++ int maxLengthOption = 0;
++ bool primaryKeyOption = false;
++ bool nullOption = false;
++ bool uniqueOption = false;
++ const int infoIndex = meta->indexOfClassInfo(meta->property(i).name());
++ if (infoIndex >= 0)
++ {
++ QMap<QString, QString> options = parseOptions(meta->classInfo(infoIndex).value());
++ QMapIterator<QString, QString> option(options);
++ while (option.hasNext()) {
++ option.next();
++ const QString value = option.value();
++ if (option.key() == "auto_increment")
++ autoIncrementOption = (value.toLower() == "true" || value == "1");
++ else if (option.key() == "db_column")
++ dbColumnOption = value;
++ else if (option.key() == "db_index")
++ dbIndexOption = (value.toLower() == "true" || value == "1");
++ else if (option.key() == "ignore_field")
++ ignoreFieldOption = (value.toLower() == "true" || value == "1");
++ else if (option.key() == "max_length")
++ maxLengthOption = value.toInt();
++ else if (option.key() == "null")
++ nullOption = (value.toLower() == "true" || value == "1");
++ else if (option.key() == "primary_key")
++ primaryKeyOption = (value.toLower() == "true" || value == "1");
++ else if (option.key() == "unique")
++ uniqueOption = (value.toLower() == "true" || value == "1");
++ }
++ }
++
++ // ignore field
++ if (ignoreFieldOption)
++ continue;
++
++ // foreign field
++ if (typeName.endsWith("*"))
++ {
++ const QByteArray fkName = meta->property(i).name();
++ const QString fkModel = typeName.left(typeName.size() - 1);
++ d->foreignFields.insert(fkName, fkModel);
++
++ QDjangoMetaField field;
++ field.d->name = fkName + "_id";
++ // FIXME : the key is not necessarily an INTEGER field, we should
++ // probably perform a lookup on the foreign model, but are we sure
++ // it is already registered?
++ field.d->type = QVariant::Int;
++ field.d->foreignModel = fkModel;
++ field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption;
++ field.d->index = true;
++ field.d->null = nullOption;
++ if (primaryKeyOption) {
++ field.d->autoIncrement = autoIncrementOption;
++ field.d->unique = true;
++ d->primaryKey = field.d->name;
++ } else if (uniqueOption) {
++ field.d->unique = true;
++ }
++ d->localFields << field;
++ continue;
++ }
++
++ // local field
++ QDjangoMetaField field;
++ field.d->name = meta->property(i).name();
++ field.d->type = meta->property(i).type();
++ field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption;
++ field.d->index = dbIndexOption;
++ field.d->maxLength = maxLengthOption;
++ field.d->null = nullOption;
++ if (primaryKeyOption) {
++ field.d->autoIncrement = autoIncrementOption;
++ field.d->unique = true;
++ d->primaryKey = field.d->name;
++ } else if (uniqueOption) {
++ field.d->unique = true;
++ }
++
++ d->localFields << field;
++ }
++
++ // automatic primary key
++ if (d->primaryKey.isEmpty()) {
++ QDjangoMetaField field;
++ field.d->name = "id";
++ field.d->type = QVariant::Int;
++ field.d->db_column = "id";
++ field.d->autoIncrement = true;
++ field.d->unique = true;
++ d->localFields.prepend(field);
++ d->primaryKey = field.d->name;
++ }
+
+- // parse table options
+- const int optionsIndex = meta->indexOfClassInfo("__meta__");
+- if (optionsIndex >= 0) {
+- QMap<QString, QString> options = parseOptions(meta->classInfo(optionsIndex).value());
+- QMapIterator<QString, QString> option(options);
+- while (option.hasNext()) {
+- option.next();
+- if (option.key() == "db_table")
+- d->table = option.value();
+- }
+- }
+-
+- const int count = meta->propertyCount();
+- for(int i = QObject::staticMetaObject.propertyCount(); i < count; ++i)
+- {
+- QString typeName = meta->property(i).typeName();
+- if (!qstrcmp(meta->property(i).name(), "pk"))
+- continue;
+-
+- // parse field options
+- bool autoIncrementOption = false;
+- QString dbColumnOption;
+- bool dbIndexOption = false;
+- bool ignoreFieldOption = false;
+- int maxLengthOption = 0;
+- bool primaryKeyOption = false;
+- bool nullOption = false;
+- bool uniqueOption = false;
+- const int infoIndex = meta->indexOfClassInfo(meta->property(i).name());
+- if (infoIndex >= 0)
+- {
+- QMap<QString, QString> options = parseOptions(meta->classInfo(infoIndex).value());
+- QMapIterator<QString, QString> option(options);
+- while (option.hasNext()) {
+- option.next();
+- const QString value = option.value();
+- if (option.key() == "auto_increment")
+- autoIncrementOption = (value.toLower() == "true" || value == "1");
+- else if (option.key() == "db_column")
+- dbColumnOption = value;
+- else if (option.key() == "db_index")
+- dbIndexOption = (value.toLower() == "true" || value == "1");
+- else if (option.key() == "ignore_field")
+- ignoreFieldOption = (value.toLower() == "true" || value == "1");
+- else if (option.key() == "max_length")
+- maxLengthOption = value.toInt();
+- else if (option.key() == "null")
+- nullOption = (value.toLower() == "true" || value == "1");
+- else if (option.key() == "primary_key")
+- primaryKeyOption = (value.toLower() == "true" || value == "1");
+- else if (option.key() == "unique")
+- uniqueOption = (value.toLower() == "true" || value == "1");
+- }
+- }
+-
+- // ignore field
+- if (ignoreFieldOption)
+- continue;
+-
+- // foreign field
+- if (typeName.endsWith("*"))
+- {
+- const QByteArray fkName = meta->property(i).name();
+- const QString fkModel = typeName.left(typeName.size() - 1);
+- d->foreignFields.insert(fkName, fkModel);
+-
+- QDjangoMetaField field;
+- field.d->name = fkName + "_id";
+- // FIXME : the key is not necessarily an INTEGER field, we should
+- // probably perform a lookup on the foreign model, but are we sure
+- // it is already registered?
+- field.d->type = QVariant::Int;
+- field.d->foreignModel = fkModel;
+- field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption;
+- field.d->index = true;
+- field.d->null = nullOption;
+- d->localFields << field;
+- continue;
+- }
+-
+- // local field
+- QDjangoMetaField field;
+- field.d->name = meta->property(i).name();
+- field.d->type = meta->property(i).type();
+- field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption;
+- field.d->index = dbIndexOption;
+- field.d->maxLength = maxLengthOption;
+- field.d->null = nullOption;
+- if (primaryKeyOption) {
+- field.d->autoIncrement = autoIncrementOption;
+- field.d->unique = true;
+- d->primaryKey = field.d->name;
+- } else if (uniqueOption) {
+- field.d->unique = true;
+- }
+-
+- d->localFields << field;
+- }
+-
+- // automatic primary key
+- if (d->primaryKey.isEmpty()) {
+- QDjangoMetaField field;
+- field.d->name = "id";
+- field.d->type = QVariant::Int;
+- field.d->db_column = "id";
+- field.d->autoIncrement = true;
+- field.d->unique = true;
+- d->localFields.prepend(field);
+- d->primaryKey = field.d->name;
+- }
+-
+ }
+
+ /*!
+- Constructs a copy of \a other.
++ Constructs a copy of \a other.
+ */
+ QDjangoMetaModel::QDjangoMetaModel(const QDjangoMetaModel &other)
+- : d(other.d)
++ : d(other.d)
+ {
+ }
+
+ /*!
+- Destroys the meta model.
++ Destroys the meta model.
+ */
+ QDjangoMetaModel::~QDjangoMetaModel()
+ {
+ }
+
+ /*!
+- Assigns \a other to this meta model.
++ Assigns \a other to this meta model.
+ */
+ QDjangoMetaModel& QDjangoMetaModel::operator=(const QDjangoMetaModel& other)
+ {
+- d = other.d;
+- return *this;
++ d = other.d;
++ return *this;
+ }
+
+ /*!
+- Creates the database table for this QDjangoMetaModel.
++ Creates the database table for this QDjangoMetaModel.
+ */
+ bool QDjangoMetaModel::createTable() const
+ {
+- QSqlDatabase db = QDjango::database();
+- QSqlDriver *driver = db.driver();
+- const QString driverName = db.driverName();
+-
+- QStringList propSql;
+- const QString quotedTable = db.driver()->escapeIdentifier(d->table, QSqlDriver::TableName);
+- foreach (const QDjangoMetaField &field, d->localFields)
+- {
+- QString fieldSql = driver->escapeIdentifier(field.column(), QSqlDriver::FieldName);
+- switch (field.d->type) {
+- case QVariant::Bool:
+- fieldSql += " BOOLEAN";
+- break;
+- case QVariant::ByteArray:
+- if (driverName == QLatin1String("QPSQL"))
+- fieldSql += " BYTEA";
+- else {
+- fieldSql += " BLOB";
+- if (field.d->maxLength > 0)
+- fieldSql += QString("(%1)").arg(field.d->maxLength);
+- }
+- break;
+- case QVariant::Date:
+- fieldSql += " DATE";
+- break;
+- case QVariant::DateTime:
+- if (driverName == QLatin1String("QPSQL"))
+- fieldSql += " TIMESTAMP";
+- else
+- fieldSql += " DATETIME";
+- break;
+- case QVariant::Double:
+- fieldSql += " REAL";
+- break;
+- case QVariant::Int:
+- fieldSql += " INTEGER";
+- break;
+- case QVariant::LongLong:
+- fieldSql += " BIGINT";
+- break;
+- case QVariant::String:
+- if (field.d->maxLength > 0)
+- fieldSql += QString(" VARCHAR(%1)").arg(field.d->maxLength);
+- else
+- fieldSql += " TEXT";
+- break;
+- case QVariant::Time:
+- fieldSql += " TIME";
+- break;
+- default:
+- qWarning() << "Unhandled type" << field.d->type << "for property" << field.d->name;
+- continue;
+- }
+-
+- if (!field.d->null)
+- fieldSql += " NOT NULL";
+-
+- // primary key
+- if (field.d->name == d->primaryKey)
+- fieldSql += " PRIMARY KEY";
+-
+- // auto-increment is backend specific
+- if (field.d->autoIncrement) {
+- if (driverName == QLatin1String("QSQLITE") ||
+- driverName == QLatin1String("QSQLITE2"))
+- fieldSql += QLatin1String(" AUTOINCREMENT");
+- else if (driverName == QLatin1String("QMYSQL"))
+- fieldSql += QLatin1String(" AUTO_INCREMENT");
+- else if (driverName == QLatin1String("QPSQL"))
+- fieldSql = driver->escapeIdentifier(field.column(), QSqlDriver::FieldName) + " SERIAL PRIMARY KEY";
+- }
+-
+- // foreign key
+- if (!field.d->foreignModel.isEmpty())
+- {
+- const QDjangoMetaModel foreignMeta = QDjango::metaModel(field.d->foreignModel);
+- const QDjangoMetaField foreignField = foreignMeta.localField("pk");
+- fieldSql += QString(" REFERENCES %1 (%2)").arg(
+- driver->escapeIdentifier(foreignMeta.d->table, QSqlDriver::TableName),
+- driver->escapeIdentifier(foreignField.column(), QSqlDriver::FieldName));
+- }
+- propSql << fieldSql;
+- }
+-
+- // create table
+- QDjangoQuery createQuery(db);
+- if (!createQuery.exec(QString("CREATE TABLE %1 (%2)").arg(
+- quotedTable,
+- propSql.join(", "))))
+- return false;
+-
+- // create indices
+- foreach (const QDjangoMetaField &field, d->localFields) {
+- if (field.d->index && !field.d->unique) {
+- const QString indexName = d->table + "_" + field.column();
+- if (!createQuery.exec(QString("CREATE INDEX %1 ON %2 (%3)").arg(
+- // FIXME : how should we escape an index name?
+- driver->escapeIdentifier(indexName, QSqlDriver::FieldName),
+- quotedTable,
+- driver->escapeIdentifier(field.column(), QSqlDriver::FieldName))))
+- return false;
+- }
+- }
++ QSqlDatabase db = QDjango::database();
++ QSqlDriver *driver = db.driver();
++ const QString driverName = db.driverName();
++
++ QStringList propSql;
++ const QString quotedTable = db.driver()->escapeIdentifier(d->table, QSqlDriver::TableName);
++ foreach (const QDjangoMetaField &field, d->localFields)
++ {
++ QString fieldSql = driver->escapeIdentifier(field.column(), QSqlDriver::FieldName);
++ switch (field.d->type) {
++ case QVariant::Bool:
++ fieldSql += " BOOLEAN";
++ break;
++ case QVariant::ByteArray:
++ if (driverName == QLatin1String("QPSQL"))
++ fieldSql += " BYTEA";
++ else {
++ fieldSql += " BLOB";
++ if (field.d->maxLength > 0)
++ fieldSql += QString("(%1)").arg(field.d->maxLength);
++ }
++ break;
++ case QVariant::Date:
++ fieldSql += " DATE";
++ break;
++ case QVariant::DateTime:
++ if (driverName == QLatin1String("QPSQL"))
++ fieldSql += " TIMESTAMP";
++ else
++ fieldSql += " DATETIME";
++ break;
++ case QVariant::Double:
++ fieldSql += " REAL";
++ break;
++ case QVariant::Int:
++ fieldSql += " INTEGER";
++ break;
++ case QVariant::LongLong:
++ fieldSql += " BIGINT";
++ break;
++ case QVariant::String:
++ if (field.d->maxLength > 0)
++ fieldSql += QString(" VARCHAR(%1)").arg(field.d->maxLength);
++ else
++ fieldSql += " TEXT";
++ break;
++ case QVariant::Time:
++ fieldSql += " TIME";
++ break;
++ default:
++ qWarning() << "Unhandled type" << field.d->type << "for property" << field.d->name;
++ continue;
++ }
++
++ if (!field.d->null)
++ fieldSql += " NOT NULL";
++
++ // primary key
++ if (field.d->name == d->primaryKey)
++ fieldSql += " PRIMARY KEY";
++
++ // auto-increment is backend specific
++ if (field.d->autoIncrement) {
++ if (driverName == QLatin1String("QSQLITE") ||
++ driverName == QLatin1String("QSQLITE2"))
++ fieldSql += QLatin1String(" AUTOINCREMENT");
++ else if (driverName == QLatin1String("QMYSQL"))
++ fieldSql += QLatin1String(" AUTO_INCREMENT");
++ else if (driverName == QLatin1String("QPSQL"))
++ fieldSql = driver->escapeIdentifier(field.column(), QSqlDriver::FieldName) + " SERIAL PRIMARY KEY";
++ }
++
++ // foreign key
++ if (!field.d->foreignModel.isEmpty())
++ {
++ const QDjangoMetaModel foreignMeta = QDjango::metaModel(field.d->foreignModel);
++ const QDjangoMetaField foreignField = foreignMeta.localField("pk");
++ fieldSql += QString(" REFERENCES %1 (%2)").arg(
++ driver->escapeIdentifier(foreignMeta.d->table, QSqlDriver::TableName),
++ driver->escapeIdentifier(foreignField.column(), QSqlDriver::FieldName));
++ }
++ propSql << fieldSql;
++ }
++
++ // create table
++ QDjangoQuery createQuery(db);
++ if (!createQuery.exec(QString("CREATE TABLE %1 (%2)").arg(
++ quotedTable,
++ propSql.join(", "))))
++ return false;
++
++ // create indices
++ foreach (const QDjangoMetaField &field, d->localFields) {
++ if (field.d->index && !field.d->unique) {
++ const QString indexName = d->table + "_" + field.column();
++ if (!createQuery.exec(QString("CREATE INDEX %1 ON %2 (%3)").arg(
++ // FIXME : how should we escape an index name?
++ driver->escapeIdentifier(indexName, QSqlDriver::FieldName),
++ quotedTable,
++ driver->escapeIdentifier(field.column(), QSqlDriver::FieldName))))
++ return false;
++ }
++ }
+
+- return true;
++ return true;
+ }
+
+ /*!
+- Drops the database table for this QDjangoMetaModel.
++ Drops the database table for this QDjangoMetaModel.
+ */
+ bool QDjangoMetaModel::dropTable() const
+ {
+- QSqlDatabase db = QDjango::database();
++ QSqlDatabase db = QDjango::database();
+
+- QDjangoQuery query(db);
+- return query.exec(QString("DROP TABLE %1").arg(
+- db.driver()->escapeIdentifier(d->table, QSqlDriver::TableName)));
++ QDjangoQuery query(db);
++ return query.exec(QString("DROP TABLE %1").arg(
++ db.driver()->escapeIdentifier(d->table, QSqlDriver::TableName)));
+ }
+
+ /*!
+- Retrieves the QDjangoModel pointed to by the given foreign-key.
++ Retrieves the QDjangoModel pointed to by the given foreign-key.
+
+- \param model
+- \param name
++ \param model
++ \param name
+ */
+ QObject *QDjangoMetaModel::foreignKey(const QObject *model, const char *name) const
+ {
+- const QByteArray prop(name);
+- QObject *foreign = model->property(prop + "_ptr").value<QObject*>();
+- if (!foreign)
+- return 0;
+-
+- // if the foreign object was not loaded yet, do it now
+- const QString foreignClass = d->foreignFields[prop];
+- const QDjangoMetaModel foreignMeta = QDjango::metaModel(foreignClass);
+- const QVariant foreignPk = model->property(prop + "_id");
+- if (foreign->property(foreignMeta.primaryKey()) != foreignPk)
+- {
+- QDjangoQuerySetPrivate qs(foreignClass);
+- qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, foreignPk));
+- qs.sqlFetch();
+- if (qs.properties.size() != 1 || !qs.sqlLoad(foreign, 0))
+- return 0;
+- }
+- return foreign;
++ const QByteArray prop(name);
++ QObject *foreign = model->property(prop + "_ptr").value<QObject*>();
++ if (!foreign)
++ return 0;
++
++ // if the foreign object was not loaded yet, do it now
++ const QString foreignClass = d->foreignFields[prop];
++ const QDjangoMetaModel foreignMeta = QDjango::metaModel(foreignClass);
++ const QVariant foreignPk = model->property(prop + "_id");
++ if (foreign->property(foreignMeta.primaryKey()) != foreignPk)
++ {
++ QDjangoQuerySetPrivate qs(foreignClass);
++ qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, foreignPk));
++ qs.sqlFetch();
++ if (qs.properties.size() != 1 || !qs.sqlLoad(foreign, 0))
++ return 0;
++ }
++ return foreign;
+ }
+
+ /*!
+- Sets the QDjangoModel pointed to by the given foreign-key.
+-
+- \param model
+- \param name
+- \param value
++ Sets the QDjangoModel pointed to by the given foreign-key.
++
++ \param model
++ \param name
++ \param value
+
+- \note The \c model will take ownership of the given \c value.
++ \note The \c model will take ownership of the given \c value.
+ */
+ void QDjangoMetaModel::setForeignKey(QObject *model, const char *name, QObject *value) const
+ {
+- const QByteArray prop(name);
+- QObject *old = model->property(prop + "_ptr").value<QObject*>();
+- if (old == value)
+- return;
+-
+- // store the new pointer and update the foreign key
+- model->setProperty(prop + "_ptr", qVariantFromValue(value));
+- if (value)
+- {
+- const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[prop]);
+- model->setProperty(prop + "_id", value->property(foreignMeta.primaryKey()));
+- } else {
+- model->setProperty(prop + "_id", QVariant());
+- }
++ const QByteArray prop(name);
++ QObject *old = model->property(prop + "_ptr").value<QObject*>();
++ if (old == value)
++ return;
++
++ // store the new pointer and update the foreign key
++ model->setProperty(prop + "_ptr", qVariantFromValue(value));
++ if (value)
++ {
++ const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[prop]);
++ model->setProperty(prop + "_id", value->property(foreignMeta.primaryKey()));
++ } else {
++ model->setProperty(prop + "_id", QVariant());
++ }
+ }
+
+ /*!
+- Loads the given properties into a \a model instance.
++ Loads the given properties into a \a model instance.
+ */
+ void QDjangoMetaModel::load(QObject *model, const QVariantList &properties, int &pos) const
+ {
+- // process local fields
+- foreach (const QDjangoMetaField &field, d->localFields)
+- model->setProperty(field.d->name, properties.at(pos++));
+-
+- // process foreign fields
+- if (pos >= properties.size())
+- return;
+- foreach (const QByteArray &fkName, d->foreignFields.keys())
+- {
+- QObject *object = model->property(fkName + "_ptr").value<QObject*>();
+- if (object)
+- {
+- const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[fkName]);
+- foreignMeta.load(object, properties, pos);
+- }
+- }
++ // process local fields
++ foreach (const QDjangoMetaField &field, d->localFields)
++ model->setProperty(field.d->name, properties.at(pos++));
++
++ // process foreign fields
++ if (pos >= properties.size())
++ return;
++ foreach (const QByteArray &fkName, d->foreignFields.keys())
++ {
++ QObject *object = model->property(fkName + "_ptr").value<QObject*>();
++ if (object)
++ {
++ const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[fkName]);
++ foreignMeta.load(object, properties, pos);
++ }
++ }
+ }
+
+ /*!
+- Returns the foreign field mapping.
++ Returns the foreign field mapping.
+ */
+ QMap<QByteArray, QString> QDjangoMetaModel::foreignFields() const
+ {
+- return d->foreignFields;
++ return d->foreignFields;
+ }
+
+ /*!
+- Return the local field with the specified \a name.
++ Return the local field with the specified \a name.
+ */
+ QDjangoMetaField QDjangoMetaModel::localField(const QString &name) const
+ {
+- const QString fieldName = (name == "pk") ? d->primaryKey : name;
+- foreach (const QDjangoMetaField &field, d->localFields) {
+- if (field.d->name == fieldName)
+- return field;
+- }
+- return QDjangoMetaField();
++ const QString fieldName = (name == "pk") ? d->primaryKey : name;
++ foreach (const QDjangoMetaField &field, d->localFields) {
++ if (field.d->name == fieldName)
++ return field;
++ }
++ return QDjangoMetaField();
+ }
+
+ /*!
+- Returns the list of local fields.
++ Returns the list of local fields.
+ */
+ QList<QDjangoMetaField> QDjangoMetaModel::localFields() const
+ {
+- return d->localFields;
++ return d->localFields;
+ }
+
+ /*!
+- Returns the name of the primary key for the current QDjangoMetaModel.
++ Returns the name of the primary key for the current QDjangoMetaModel.
+ */
+ QByteArray QDjangoMetaModel::primaryKey() const
+ {
+- return d->primaryKey;
++ return d->primaryKey;
+ }
+
+ /*!
+- Returns the name of the database table.
++ Returns the name of the database table.
+ */
+ QString QDjangoMetaModel::table() const
+ {
+- return d->table;
++ return d->table;
+ }
+
+ /*!
+- Removes the given \a model instance from the database.
++ Removes the given \a model instance from the database.
+ */
+ bool QDjangoMetaModel::remove(QObject *model) const
+ {
+- const QVariant pk = model->property(d->primaryKey);
+- QDjangoQuerySetPrivate qs(model->metaObject()->className());
+- qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, pk));
+- return qs.sqlDelete();
++ const QVariant pk = model->property(d->primaryKey);
++ QDjangoQuerySetPrivate qs(model->metaObject()->className());
++ qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, pk));
++ return qs.sqlDelete();
+ }
+
+ /*!
+- Saves the given \a model instance to the database.
++ Saves the given \a model instance to the database.
+
+- \return true if saving succeeded, false otherwise
++ \return true if saving succeeded, false otherwise
+ */
+ bool QDjangoMetaModel::save(QObject *model) const
+ {
+- // find primary key
+- const QDjangoMetaField primaryKey = localField("pk");
+- const QVariant pk = model->property(d->primaryKey);
+- if (!pk.isNull() && !(primaryKey.d->type == QVariant::Int && !pk.toInt()))
+- {
+- QSqlDatabase db = QDjango::database();
+- QDjangoQuery query(db);
+- query.prepare(QString("SELECT 1 AS a FROM %1 WHERE %2 = ?").arg(
+- db.driver()->escapeIdentifier(d->table, QSqlDriver::FieldName),
+- db.driver()->escapeIdentifier(primaryKey.column(), QSqlDriver::FieldName)));
+- query.addBindValue(pk);
+- if (query.exec() && query.next())
+- {
+- // prepare data
+- QVariantMap fields;
+- foreach (const QDjangoMetaField &field, d->localFields) {
+- if (field.d->name != d->primaryKey) {
+- const QVariant value = model->property(field.d->name);
+- fields.insert(field.d->name, field.toDatabase(value));
+- }
+- }
+-
+- // perform UPDATE
+- QDjangoQuerySetPrivate qs(model->metaObject()->className());
+- qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, pk));
+- return qs.sqlUpdate(fields) != -1;
+- }
+- }
+-
+- // prepare data
+- QVariantMap fields;
+- foreach (const QDjangoMetaField &field, d->localFields) {
+- if (!field.d->autoIncrement) {
+- const QVariant value = model->property(field.d->name);
+- fields.insert(field.name(), field.toDatabase(value));
+- }
+- }
+-
+- // perform INSERT
+- QVariant insertId;
+- QDjangoQuerySetPrivate qs(model->metaObject()->className());
+- if (!qs.sqlInsert(fields, &insertId))
+- return false;
+-
+- // fetch autoincrement pk
+- if (primaryKey.d->autoIncrement)
+- model->setProperty(d->primaryKey, insertId);
+- return true;
++ // find primary key
++ const QDjangoMetaField primaryKey = localField("pk");
++ const QVariant pk = model->property(d->primaryKey);
++ if (!pk.isNull() && !(primaryKey.d->type == QVariant::Int && !pk.toInt()))
++ {
++ QSqlDatabase db = QDjango::database();
++ QDjangoQuery query(db);
++ query.prepare(QString("SELECT 1 AS a FROM %1 WHERE %2 = ?").arg(
++ db.driver()->escapeIdentifier(d->table, QSqlDriver::FieldName),
++ db.driver()->escapeIdentifier(primaryKey.column(), QSqlDriver::FieldName)));
++ query.addBindValue(pk);
++ if (query.exec() && query.next())
++ {
++ // prepare data
++ QVariantMap fields;
++ foreach (const QDjangoMetaField &field, d->localFields) {
++ if (field.d->name != d->primaryKey) {
++ const QVariant value = model->property(field.d->name);
++ fields.insert(field.d->name, field.toDatabase(value));
++ }
++ }
++
++ // perform UPDATE
++ QDjangoQuerySetPrivate qs(model->metaObject()->className());
++ qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, pk));
++ return qs.sqlUpdate(fields) != -1;
++ }
++ }
++
++ // prepare data
++ QVariantMap fields;
++ foreach (const QDjangoMetaField &field, d->localFields) {
++ if (!field.d->autoIncrement) {
++ const QVariant value = model->property(field.d->name);
++ fields.insert(field.name(), field.toDatabase(value));
++ }
++ }
++
++ // perform INSERT
++ QVariant insertId;
++ QDjangoQuerySetPrivate qs(model->metaObject()->className());
++ if (!qs.sqlInsert(fields, &insertId))
++ return false;
++
++ // fetch autoincrement pk
++ if (primaryKey.d->autoIncrement)
++ model->setProperty(d->primaryKey, insertId);
++ return true;
+ }
+"""]]
+
+
+## After editing with Emacs
+
+Note the additional `@@ ... @@` lines:
+[[!format diff """
+--- qdjango-0.2.6.orig/src/db/QDjangoMetaModel.cpp 2012-09-09 07:34:12.000000000 +0200
++++ qdjango-0.2.6/src/db/QDjangoMetaModel.cpp 2012-09-09 08:35:54.000000000 +0200
+@@ -27,15 +27,15 @@
+ class QDjangoMetaFieldPrivate : public QSharedData
+ {
+ public:
+- QDjangoMetaFieldPrivate();
++ QDjangoMetaFieldPrivate();
+
+- bool autoIncrement;
+- QString db_column;
+- QString foreignModel;
+- bool index;
+- int maxLength;
+- QByteArray name;
+- bool null;
+- QVariant::Type type;
+- bool unique;
++ bool autoIncrement;
++ QString db_column;
++ QString foreignModel;
++ bool index;
++ int maxLength;
++ QByteArray name;
++ bool null;
++ QVariant::Type type;
++ bool unique;
+ };
+@@ -42,9 +42,9 @@
+
+ QDjangoMetaFieldPrivate::QDjangoMetaFieldPrivate()
+- : autoIncrement(false),
+- index(false),
+- maxLength(0),
+- null(false),
+- unique(false)
++ : autoIncrement(false),
++ index(false),
++ maxLength(0),
++ null(false),
++ unique(false)
+ {
+ }
+@@ -51,8 +51,8 @@
+
+ /*!
+- Constructs a new QDjangoMetaField.
++ Constructs a new QDjangoMetaField.
+ */
+ QDjangoMetaField::QDjangoMetaField()
+ {
+- d = new QDjangoMetaFieldPrivate;
++ d = new QDjangoMetaFieldPrivate;
+ }
+@@ -59,8 +59,8 @@
+
+ /*!
+- Constructs a copy of \a other.
++ Constructs a copy of \a other.
+ */
+ QDjangoMetaField::QDjangoMetaField(const QDjangoMetaField &other)
+- : d(other.d)
++ : d(other.d)
+ {
+ }
+@@ -67,7 +67,7 @@
+
+ /*!
+- Destroys the meta field.
++ Destroys the meta field.
+ */
+ QDjangoMetaField::~QDjangoMetaField()
+ {
+ }
+@@ -74,9 +74,9 @@
+
+ /*!
+- Assigns \a other to this meta field.
++ Assigns \a other to this meta field.
+ */
+ QDjangoMetaField& QDjangoMetaField::operator=(const QDjangoMetaField& other)
+ {
+- d = other.d;
+- return *this;
++ d = other.d;
++ return *this;
+ }
+@@ -83,8 +83,8 @@
+
+ /*!
+- Returns the database column for this meta field.
++ Returns the database column for this meta field.
+ */
+ QString QDjangoMetaField::column() const
+ {
+- return d->db_column;
++ return d->db_column;
+ }
+@@ -91,8 +91,8 @@
+
+ /*!
+- Returns true if this is a valid field.
++ Returns true if this is a valid field.
+ */
+ bool QDjangoMetaField::isValid() const
+ {
+- return !d->name.isEmpty();
++ return !d->name.isEmpty();
+ }
+@@ -99,8 +99,8 @@
+
+ /*!
+- Returns name of this meta field.
++ Returns name of this meta field.
+ */
+ QString QDjangoMetaField::name() const
+ {
+- return QString::fromLatin1(d->name);
++ return QString::fromLatin1(d->name);
+ }
+@@ -107,14 +107,14 @@
+
+ /*!
+- Transforms the given field value for database storage.
++ Transforms the given field value for database storage.
+ */
+ QVariant QDjangoMetaField::toDatabase(const QVariant &value) const
+ {
+- if (d->type == QVariant::String && !d->null && value.isNull())
+- return QString("");
+- else if (!d->foreignModel.isEmpty() && d->type == QVariant::Int && d->null && !value.toInt()) {
+- // store 0 foreign key as NULL if the field is NULL
+- return QVariant();
+- } else
+- return value;
++ if (d->type == QVariant::String && !d->null && value.isNull())
++ return QString("");
++ else if (!d->foreignModel.isEmpty() && d->type == QVariant::Int && d->null && !value.toInt()) {
++ // store 0 foreign key as NULL if the field is NULL
++ return QVariant();
++ } else
++ return value;
+ }
+@@ -121,15 +121,15 @@
+
+ static QMap<QString, QString> parseOptions(const char *value)
+ {
+- QMap<QString, QString> options;
+- QStringList items = QString::fromUtf8(value).split(' ');
+- foreach (const QString &item, items) {
+- QStringList assign = item.split('=');
+- if (assign.size() == 2) {
+- options[assign[0].toLower()] = assign[1];
+- } else {
+- qWarning() << "Could not parse option" << item;
+- }
+- }
+- return options;
++ QMap<QString, QString> options;
++ QStringList items = QString::fromUtf8(value).split(' ');
++ foreach (const QString &item, items) {
++ QStringList assign = item.split('=');
++ if (assign.size() == 2) {
++ options[assign[0].toLower()] = assign[1];
++ } else {
++ qWarning() << "Could not parse option" << item;
++ }
++ }
++ return options;
+ }
+@@ -136,12 +136,12 @@
+
+ class QDjangoMetaModelPrivate : public QSharedData
+ {
+ public:
+- QList<QDjangoMetaField> localFields;
+- QMap<QByteArray, QString> foreignFields;
+- QByteArray primaryKey;
+- QString table;
++ QList<QDjangoMetaField> localFields;
++ QMap<QByteArray, QString> foreignFields;
++ QByteArray primaryKey;
++ QString table;
+ };
+
+ /*!
+- Constructs a new QDjangoMetaModel by inspecting the given \a model instance.
++ Constructs a new QDjangoMetaModel by inspecting the given \a model instance.
+@@ -148,141 +148,148 @@
+ */
+ QDjangoMetaModel::QDjangoMetaModel(const QObject *model)
+- : d(new QDjangoMetaModelPrivate)
++ : d(new QDjangoMetaModelPrivate)
+ {
+- if (!model)
+- return;
++ if (!model)
++ return;
+
+- const QMetaObject* meta = model->metaObject();
+- d->table = QString(meta->className()).toLower().toLatin1();
++ const QMetaObject* meta = model->metaObject();
++ d->table = QString(meta->className()).toLower().toLatin1();
++
++ // parse table options
++ const int optionsIndex = meta->indexOfClassInfo("__meta__");
++ if (optionsIndex >= 0) {
++ QMap<QString, QString> options = parseOptions(meta->classInfo(optionsIndex).value());
++ QMapIterator<QString, QString> option(options);
++ while (option.hasNext()) {
++ option.next();
++ if (option.key() == "db_table")
++ d->table = option.value();
++ }
++ }
++
++ const int count = meta->propertyCount();
++ for(int i = QObject::staticMetaObject.propertyCount(); i < count; ++i)
++ {
++ QString typeName = meta->property(i).typeName();
++ if (!qstrcmp(meta->property(i).name(), "pk"))
++ continue;
++
++ // parse field options
++ bool autoIncrementOption = false;
++ QString dbColumnOption;
++ bool dbIndexOption = false;
++ bool ignoreFieldOption = false;
++ int maxLengthOption = 0;
++ bool primaryKeyOption = false;
++ bool nullOption = false;
++ bool uniqueOption = false;
++ const int infoIndex = meta->indexOfClassInfo(meta->property(i).name());
++ if (infoIndex >= 0)
++ {
++ QMap<QString, QString> options = parseOptions(meta->classInfo(infoIndex).value());
++ QMapIterator<QString, QString> option(options);
++ while (option.hasNext()) {
++ option.next();
++ const QString value = option.value();
++ if (option.key() == "auto_increment")
++ autoIncrementOption = (value.toLower() == "true" || value == "1");
++ else if (option.key() == "db_column")
++ dbColumnOption = value;
++ else if (option.key() == "db_index")
++ dbIndexOption = (value.toLower() == "true" || value == "1");
++ else if (option.key() == "ignore_field")
++ ignoreFieldOption = (value.toLower() == "true" || value == "1");
++ else if (option.key() == "max_length")
++ maxLengthOption = value.toInt();
++ else if (option.key() == "null")
++ nullOption = (value.toLower() == "true" || value == "1");
++ else if (option.key() == "primary_key")
++ primaryKeyOption = (value.toLower() == "true" || value == "1");
++ else if (option.key() == "unique")
++ uniqueOption = (value.toLower() == "true" || value == "1");
++ }
++ }
++
++ // ignore field
++ if (ignoreFieldOption)
++ continue;
++
++ // foreign field
++ if (typeName.endsWith("*"))
++ {
++ const QByteArray fkName = meta->property(i).name();
++ const QString fkModel = typeName.left(typeName.size() - 1);
++ d->foreignFields.insert(fkName, fkModel);
++
++ QDjangoMetaField field;
++ field.d->name = fkName + "_id";
++ // FIXME : the key is not necessarily an INTEGER field, we should
++ // probably perform a lookup on the foreign model, but are we sure
++ // it is already registered?
++ field.d->type = QVariant::Int;
++ field.d->foreignModel = fkModel;
++ field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption;
++ field.d->index = true;
++ field.d->null = nullOption;
++ if (primaryKeyOption) {
++ field.d->autoIncrement = autoIncrementOption;
++ field.d->unique = true;
++ d->primaryKey = field.d->name;
++ } else if (uniqueOption) {
++ field.d->unique = true;
++ }
++ d->localFields << field;
++ continue;
++ }
++
++ // local field
++ QDjangoMetaField field;
++ field.d->name = meta->property(i).name();
++ field.d->type = meta->property(i).type();
++ field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption;
++ field.d->index = dbIndexOption;
++ field.d->maxLength = maxLengthOption;
++ field.d->null = nullOption;
++ if (primaryKeyOption) {
++ field.d->autoIncrement = autoIncrementOption;
++ field.d->unique = true;
++ d->primaryKey = field.d->name;
++ } else if (uniqueOption) {
++ field.d->unique = true;
++ }
++
++ d->localFields << field;
++ }
++
++ // automatic primary key
++ if (d->primaryKey.isEmpty()) {
++ QDjangoMetaField field;
++ field.d->name = "id";
++ field.d->type = QVariant::Int;
++ field.d->db_column = "id";
++ field.d->autoIncrement = true;
++ field.d->unique = true;
++ d->localFields.prepend(field);
++ d->primaryKey = field.d->name;
++ }
+
+- // parse table options
+- const int optionsIndex = meta->indexOfClassInfo("__meta__");
+- if (optionsIndex >= 0) {
+- QMap<QString, QString> options = parseOptions(meta->classInfo(optionsIndex).value());
+- QMapIterator<QString, QString> option(options);
+- while (option.hasNext()) {
+- option.next();
+- if (option.key() == "db_table")
+- d->table = option.value();
+- }
+- }
+-
+- const int count = meta->propertyCount();
+- for(int i = QObject::staticMetaObject.propertyCount(); i < count; ++i)
+- {
+- QString typeName = meta->property(i).typeName();
+- if (!qstrcmp(meta->property(i).name(), "pk"))
+- continue;
+-
+- // parse field options
+- bool autoIncrementOption = false;
+- QString dbColumnOption;
+- bool dbIndexOption = false;
+- bool ignoreFieldOption = false;
+- int maxLengthOption = 0;
+- bool primaryKeyOption = false;
+- bool nullOption = false;
+- bool uniqueOption = false;
+- const int infoIndex = meta->indexOfClassInfo(meta->property(i).name());
+- if (infoIndex >= 0)
+- {
+- QMap<QString, QString> options = parseOptions(meta->classInfo(infoIndex).value());
+- QMapIterator<QString, QString> option(options);
+- while (option.hasNext()) {
+- option.next();
+- const QString value = option.value();
+- if (option.key() == "auto_increment")
+- autoIncrementOption = (value.toLower() == "true" || value == "1");
+- else if (option.key() == "db_column")
+- dbColumnOption = value;
+- else if (option.key() == "db_index")
+- dbIndexOption = (value.toLower() == "true" || value == "1");
+- else if (option.key() == "ignore_field")
+- ignoreFieldOption = (value.toLower() == "true" || value == "1");
+- else if (option.key() == "max_length")
+- maxLengthOption = value.toInt();
+- else if (option.key() == "null")
+- nullOption = (value.toLower() == "true" || value == "1");
+- else if (option.key() == "primary_key")
+- primaryKeyOption = (value.toLower() == "true" || value == "1");
+- else if (option.key() == "unique")
+- uniqueOption = (value.toLower() == "true" || value == "1");
+- }
+- }
+-
+- // ignore field
+- if (ignoreFieldOption)
+- continue;
+-
+- // foreign field
+- if (typeName.endsWith("*"))
+- {
+- const QByteArray fkName = meta->property(i).name();
+- const QString fkModel = typeName.left(typeName.size() - 1);
+- d->foreignFields.insert(fkName, fkModel);
+-
+- QDjangoMetaField field;
+- field.d->name = fkName + "_id";
+- // FIXME : the key is not necessarily an INTEGER field, we should
+- // probably perform a lookup on the foreign model, but are we sure
+- // it is already registered?
+- field.d->type = QVariant::Int;
+- field.d->foreignModel = fkModel;
+- field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption;
+- field.d->index = true;
+- field.d->null = nullOption;
+- d->localFields << field;
+- continue;
+- }
+-
+- // local field
+- QDjangoMetaField field;
+- field.d->name = meta->property(i).name();
+- field.d->type = meta->property(i).type();
+- field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption;
+- field.d->index = dbIndexOption;
+- field.d->maxLength = maxLengthOption;
+- field.d->null = nullOption;
+- if (primaryKeyOption) {
+- field.d->autoIncrement = autoIncrementOption;
+- field.d->unique = true;
+- d->primaryKey = field.d->name;
+- } else if (uniqueOption) {
+- field.d->unique = true;
+- }
+-
+- d->localFields << field;
+- }
+-
+- // automatic primary key
+- if (d->primaryKey.isEmpty()) {
+- QDjangoMetaField field;
+- field.d->name = "id";
+- field.d->type = QVariant::Int;
+- field.d->db_column = "id";
+- field.d->autoIncrement = true;
+- field.d->unique = true;
+- d->localFields.prepend(field);
+- d->primaryKey = field.d->name;
+- }
+-
+ }
+
+ /*!
+- Constructs a copy of \a other.
++ Constructs a copy of \a other.
+ */
+ QDjangoMetaModel::QDjangoMetaModel(const QDjangoMetaModel &other)
+- : d(other.d)
++ : d(other.d)
+ {
+ }
+
+ /*!
+- Destroys the meta model.
++ Destroys the meta model.
+ */
+ QDjangoMetaModel::~QDjangoMetaModel()
+ {
+ }
+
+ /*!
+- Assigns \a other to this meta model.
++ Assigns \a other to this meta model.
+ */
+@@ -289,6 +296,6 @@
+ QDjangoMetaModel& QDjangoMetaModel::operator=(const QDjangoMetaModel& other)
+ {
+- d = other.d;
+- return *this;
++ d = other.d;
++ return *this;
+ }
+
+@@ -295,112 +302,112 @@
+ /*!
+- Creates the database table for this QDjangoMetaModel.
++ Creates the database table for this QDjangoMetaModel.
+ */
+ bool QDjangoMetaModel::createTable() const
+ {
+- QSqlDatabase db = QDjango::database();
+- QSqlDriver *driver = db.driver();
+- const QString driverName = db.driverName();
+-
+- QStringList propSql;
+- const QString quotedTable = db.driver()->escapeIdentifier(d->table, QSqlDriver::TableName);
+- foreach (const QDjangoMetaField &field, d->localFields)
+- {
+- QString fieldSql = driver->escapeIdentifier(field.column(), QSqlDriver::FieldName);
+- switch (field.d->type) {
+- case QVariant::Bool:
+- fieldSql += " BOOLEAN";
+- break;
+- case QVariant::ByteArray:
+- if (driverName == QLatin1String("QPSQL"))
+- fieldSql += " BYTEA";
+- else {
+- fieldSql += " BLOB";
+- if (field.d->maxLength > 0)
+- fieldSql += QString("(%1)").arg(field.d->maxLength);
+- }
+- break;
+- case QVariant::Date:
+- fieldSql += " DATE";
+- break;
+- case QVariant::DateTime:
+- if (driverName == QLatin1String("QPSQL"))
+- fieldSql += " TIMESTAMP";
+- else
+- fieldSql += " DATETIME";
+- break;
+- case QVariant::Double:
+- fieldSql += " REAL";
+- break;
+- case QVariant::Int:
+- fieldSql += " INTEGER";
+- break;
+- case QVariant::LongLong:
+- fieldSql += " BIGINT";
+- break;
+- case QVariant::String:
+- if (field.d->maxLength > 0)
+- fieldSql += QString(" VARCHAR(%1)").arg(field.d->maxLength);
+- else
+- fieldSql += " TEXT";
+- break;
+- case QVariant::Time:
+- fieldSql += " TIME";
+- break;
+- default:
+- qWarning() << "Unhandled type" << field.d->type << "for property" << field.d->name;
+- continue;
+- }
+-
+- if (!field.d->null)
+- fieldSql += " NOT NULL";
+-
+- // primary key
+- if (field.d->name == d->primaryKey)
+- fieldSql += " PRIMARY KEY";
+-
+- // auto-increment is backend specific
+- if (field.d->autoIncrement) {
+- if (driverName == QLatin1String("QSQLITE") ||
+- driverName == QLatin1String("QSQLITE2"))
+- fieldSql += QLatin1String(" AUTOINCREMENT");
+- else if (driverName == QLatin1String("QMYSQL"))
+- fieldSql += QLatin1String(" AUTO_INCREMENT");
+- else if (driverName == QLatin1String("QPSQL"))
+- fieldSql = driver->escapeIdentifier(field.column(), QSqlDriver::FieldName) + " SERIAL PRIMARY KEY";
+- }
+-
+- // foreign key
+- if (!field.d->foreignModel.isEmpty())
+- {
+- const QDjangoMetaModel foreignMeta = QDjango::metaModel(field.d->foreignModel);
+- const QDjangoMetaField foreignField = foreignMeta.localField("pk");
+- fieldSql += QString(" REFERENCES %1 (%2)").arg(
+- driver->escapeIdentifier(foreignMeta.d->table, QSqlDriver::TableName),
+- driver->escapeIdentifier(foreignField.column(), QSqlDriver::FieldName));
+- }
+- propSql << fieldSql;
+- }
+-
+- // create table
+- QDjangoQuery createQuery(db);
+- if (!createQuery.exec(QString("CREATE TABLE %1 (%2)").arg(
+- quotedTable,
+- propSql.join(", "))))
+- return false;
+-
+- // create indices
+- foreach (const QDjangoMetaField &field, d->localFields) {
+- if (field.d->index && !field.d->unique) {
+- const QString indexName = d->table + "_" + field.column();
+- if (!createQuery.exec(QString("CREATE INDEX %1 ON %2 (%3)").arg(
+- // FIXME : how should we escape an index name?
+- driver->escapeIdentifier(indexName, QSqlDriver::FieldName),
+- quotedTable,
+- driver->escapeIdentifier(field.column(), QSqlDriver::FieldName))))
+- return false;
+- }
+- }
++ QSqlDatabase db = QDjango::database();
++ QSqlDriver *driver = db.driver();
++ const QString driverName = db.driverName();
++
++ QStringList propSql;
++ const QString quotedTable = db.driver()->escapeIdentifier(d->table, QSqlDriver::TableName);
++ foreach (const QDjangoMetaField &field, d->localFields)
++ {
++ QString fieldSql = driver->escapeIdentifier(field.column(), QSqlDriver::FieldName);
++ switch (field.d->type) {
++ case QVariant::Bool:
++ fieldSql += " BOOLEAN";
++ break;
++ case QVariant::ByteArray:
++ if (driverName == QLatin1String("QPSQL"))
++ fieldSql += " BYTEA";
++ else {
++ fieldSql += " BLOB";
++ if (field.d->maxLength > 0)
++ fieldSql += QString("(%1)").arg(field.d->maxLength);
++ }
++ break;
++ case QVariant::Date:
++ fieldSql += " DATE";
++ break;
++ case QVariant::DateTime:
++ if (driverName == QLatin1String("QPSQL"))
++ fieldSql += " TIMESTAMP";
++ else
++ fieldSql += " DATETIME";
++ break;
++ case QVariant::Double:
++ fieldSql += " REAL";
++ break;
++ case QVariant::Int:
++ fieldSql += " INTEGER";
++ break;
++ case QVariant::LongLong:
++ fieldSql += " BIGINT";
++ break;
++ case QVariant::String:
++ if (field.d->maxLength > 0)
++ fieldSql += QString(" VARCHAR(%1)").arg(field.d->maxLength);
++ else
++ fieldSql += " TEXT";
++ break;
++ case QVariant::Time:
++ fieldSql += " TIME";
++ break;
++ default:
++ qWarning() << "Unhandled type" << field.d->type << "for property" << field.d->name;
++ continue;
++ }
++
++ if (!field.d->null)
++ fieldSql += " NOT NULL";
++
++ // primary key
++ if (field.d->name == d->primaryKey)
++ fieldSql += " PRIMARY KEY";
++
++ // auto-increment is backend specific
++ if (field.d->autoIncrement) {
++ if (driverName == QLatin1String("QSQLITE") ||
++ driverName == QLatin1String("QSQLITE2"))
++ fieldSql += QLatin1String(" AUTOINCREMENT");
++ else if (driverName == QLatin1String("QMYSQL"))
++ fieldSql += QLatin1String(" AUTO_INCREMENT");
++ else if (driverName == QLatin1String("QPSQL"))
++ fieldSql = driver->escapeIdentifier(field.column(), QSqlDriver::FieldName) + " SERIAL PRIMARY KEY";
++ }
++
++ // foreign key
++ if (!field.d->foreignModel.isEmpty())
++ {
++ const QDjangoMetaModel foreignMeta = QDjango::metaModel(field.d->foreignModel);
++ const QDjangoMetaField foreignField = foreignMeta.localField("pk");
++ fieldSql += QString(" REFERENCES %1 (%2)").arg(
++ driver->escapeIdentifier(foreignMeta.d->table, QSqlDriver::TableName),
++ driver->escapeIdentifier(foreignField.column(), QSqlDriver::FieldName));
++ }
++ propSql << fieldSql;
++ }
++
++ // create table
++ QDjangoQuery createQuery(db);
++ if (!createQuery.exec(QString("CREATE TABLE %1 (%2)").arg(
++ quotedTable,
++ propSql.join(", "))))
++ return false;
++
++ // create indices
++ foreach (const QDjangoMetaField &field, d->localFields) {
++ if (field.d->index && !field.d->unique) {
++ const QString indexName = d->table + "_" + field.column();
++ if (!createQuery.exec(QString("CREATE INDEX %1 ON %2 (%3)").arg(
++ // FIXME : how should we escape an index name?
++ driver->escapeIdentifier(indexName, QSqlDriver::FieldName),
++ quotedTable,
++ driver->escapeIdentifier(field.column(), QSqlDriver::FieldName))))
++ return false;
++ }
++ }
+
+- return true;
++ return true;
+ }
+
+@@ -407,12 +414,12 @@
+ /*!
+- Drops the database table for this QDjangoMetaModel.
++ Drops the database table for this QDjangoMetaModel.
+ */
+ bool QDjangoMetaModel::dropTable() const
+ {
+- QSqlDatabase db = QDjango::database();
++ QSqlDatabase db = QDjango::database();
+
+- QDjangoQuery query(db);
+- return query.exec(QString("DROP TABLE %1").arg(
+- db.driver()->escapeIdentifier(d->table, QSqlDriver::TableName)));
++ QDjangoQuery query(db);
++ return query.exec(QString("DROP TABLE %1").arg(
++ db.driver()->escapeIdentifier(d->table, QSqlDriver::TableName)));
+ }
+
+@@ -419,27 +426,27 @@
+ /*!
+- Retrieves the QDjangoModel pointed to by the given foreign-key.
++ Retrieves the QDjangoModel pointed to by the given foreign-key.
+
+- \param model
+- \param name
++ \param model
++ \param name
+ */
+ QObject *QDjangoMetaModel::foreignKey(const QObject *model, const char *name) const
+ {
+- const QByteArray prop(name);
+- QObject *foreign = model->property(prop + "_ptr").value<QObject*>();
+- if (!foreign)
+- return 0;
+-
+- // if the foreign object was not loaded yet, do it now
+- const QString foreignClass = d->foreignFields[prop];
+- const QDjangoMetaModel foreignMeta = QDjango::metaModel(foreignClass);
+- const QVariant foreignPk = model->property(prop + "_id");
+- if (foreign->property(foreignMeta.primaryKey()) != foreignPk)
+- {
+- QDjangoQuerySetPrivate qs(foreignClass);
+- qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, foreignPk));
+- qs.sqlFetch();
+- if (qs.properties.size() != 1 || !qs.sqlLoad(foreign, 0))
+- return 0;
+- }
+- return foreign;
++ const QByteArray prop(name);
++ QObject *foreign = model->property(prop + "_ptr").value<QObject*>();
++ if (!foreign)
++ return 0;
++
++ // if the foreign object was not loaded yet, do it now
++ const QString foreignClass = d->foreignFields[prop];
++ const QDjangoMetaModel foreignMeta = QDjango::metaModel(foreignClass);
++ const QVariant foreignPk = model->property(prop + "_id");
++ if (foreign->property(foreignMeta.primaryKey()) != foreignPk)
++ {
++ QDjangoQuerySetPrivate qs(foreignClass);
++ qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, foreignPk));
++ qs.sqlFetch();
++ if (qs.properties.size() != 1 || !qs.sqlLoad(foreign, 0))
++ return 0;
++ }
++ return foreign;
+ }
+@@ -446,28 +453,28 @@
+
+ /*!
+- Sets the QDjangoModel pointed to by the given foreign-key.
+-
+- \param model
+- \param name
+- \param value
++ Sets the QDjangoModel pointed to by the given foreign-key.
++
++ \param model
++ \param name
++ \param value
+
+- \note The \c model will take ownership of the given \c value.
++ \note The \c model will take ownership of the given \c value.
+ */
+ void QDjangoMetaModel::setForeignKey(QObject *model, const char *name, QObject *value) const
+ {
+- const QByteArray prop(name);
+- QObject *old = model->property(prop + "_ptr").value<QObject*>();
+- if (old == value)
+- return;
+-
+- // store the new pointer and update the foreign key
+- model->setProperty(prop + "_ptr", qVariantFromValue(value));
+- if (value)
+- {
+- const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[prop]);
+- model->setProperty(prop + "_id", value->property(foreignMeta.primaryKey()));
+- } else {
+- model->setProperty(prop + "_id", QVariant());
+- }
++ const QByteArray prop(name);
++ QObject *old = model->property(prop + "_ptr").value<QObject*>();
++ if (old == value)
++ return;
++
++ // store the new pointer and update the foreign key
++ model->setProperty(prop + "_ptr", qVariantFromValue(value));
++ if (value)
++ {
++ const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[prop]);
++ model->setProperty(prop + "_id", value->property(foreignMeta.primaryKey()));
++ } else {
++ model->setProperty(prop + "_id", QVariant());
++ }
+ }
+
+@@ -474,23 +481,23 @@
+ /*!
+- Loads the given properties into a \a model instance.
++ Loads the given properties into a \a model instance.
+ */
+ void QDjangoMetaModel::load(QObject *model, const QVariantList &properties, int &pos) const
+ {
+- // process local fields
+- foreach (const QDjangoMetaField &field, d->localFields)
+- model->setProperty(field.d->name, properties.at(pos++));
+-
+- // process foreign fields
+- if (pos >= properties.size())
+- return;
+- foreach (const QByteArray &fkName, d->foreignFields.keys())
+- {
+- QObject *object = model->property(fkName + "_ptr").value<QObject*>();
+- if (object)
+- {
+- const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[fkName]);
+- foreignMeta.load(object, properties, pos);
+- }
+- }
++ // process local fields
++ foreach (const QDjangoMetaField &field, d->localFields)
++ model->setProperty(field.d->name, properties.at(pos++));
++
++ // process foreign fields
++ if (pos >= properties.size())
++ return;
++ foreach (const QByteArray &fkName, d->foreignFields.keys())
++ {
++ QObject *object = model->property(fkName + "_ptr").value<QObject*>();
++ if (object)
++ {
++ const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[fkName]);
++ foreignMeta.load(object, properties, pos);
++ }
++ }
+ }
+
+@@ -497,8 +504,8 @@
+ /*!
+- Returns the foreign field mapping.
++ Returns the foreign field mapping.
+ */
+ QMap<QByteArray, QString> QDjangoMetaModel::foreignFields() const
+ {
+- return d->foreignFields;
++ return d->foreignFields;
+ }
+
+@@ -505,13 +512,13 @@
+ /*!
+- Return the local field with the specified \a name.
++ Return the local field with the specified \a name.
+ */
+ QDjangoMetaField QDjangoMetaModel::localField(const QString &name) const
+ {
+- const QString fieldName = (name == "pk") ? d->primaryKey : name;
+- foreach (const QDjangoMetaField &field, d->localFields) {
+- if (field.d->name == fieldName)
+- return field;
+- }
+- return QDjangoMetaField();
++ const QString fieldName = (name == "pk") ? d->primaryKey : name;
++ foreach (const QDjangoMetaField &field, d->localFields) {
++ if (field.d->name == fieldName)
++ return field;
++ }
++ return QDjangoMetaField();
+ }
+
+@@ -518,8 +525,8 @@
+ /*!
+- Returns the list of local fields.
++ Returns the list of local fields.
+ */
+ QList<QDjangoMetaField> QDjangoMetaModel::localFields() const
+ {
+- return d->localFields;
++ return d->localFields;
+ }
+
+@@ -526,8 +533,8 @@
+ /*!
+- Returns the name of the primary key for the current QDjangoMetaModel.
++ Returns the name of the primary key for the current QDjangoMetaModel.
+ */
+ QByteArray QDjangoMetaModel::primaryKey() const
+ {
+- return d->primaryKey;
++ return d->primaryKey;
+ }
+
+@@ -534,8 +541,8 @@
+ /*!
+- Returns the name of the database table.
++ Returns the name of the database table.
+ */
+ QString QDjangoMetaModel::table() const
+ {
+- return d->table;
++ return d->table;
+ }
+
+@@ -542,11 +549,11 @@
+ /*!
+- Removes the given \a model instance from the database.
++ Removes the given \a model instance from the database.
+ */
+ bool QDjangoMetaModel::remove(QObject *model) const
+ {
+- const QVariant pk = model->property(d->primaryKey);
+- QDjangoQuerySetPrivate qs(model->metaObject()->className());
+- qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, pk));
+- return qs.sqlDelete();
++ const QVariant pk = model->property(d->primaryKey);
++ QDjangoQuerySetPrivate qs(model->metaObject()->className());
++ qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, pk));
++ return qs.sqlDelete();
+ }
+
+@@ -553,57 +560,57 @@
+ /*!
+- Saves the given \a model instance to the database.
++ Saves the given \a model instance to the database.
+
+- \return true if saving succeeded, false otherwise
++ \return true if saving succeeded, false otherwise
+ */
+ bool QDjangoMetaModel::save(QObject *model) const
+ {
+- // find primary key
+- const QDjangoMetaField primaryKey = localField("pk");
+- const QVariant pk = model->property(d->primaryKey);
+- if (!pk.isNull() && !(primaryKey.d->type == QVariant::Int && !pk.toInt()))
+- {
+- QSqlDatabase db = QDjango::database();
+- QDjangoQuery query(db);
+- query.prepare(QString("SELECT 1 AS a FROM %1 WHERE %2 = ?").arg(
+- db.driver()->escapeIdentifier(d->table, QSqlDriver::FieldName),
+- db.driver()->escapeIdentifier(primaryKey.column(), QSqlDriver::FieldName)));
+- query.addBindValue(pk);
+- if (query.exec() && query.next())
+- {
+- // prepare data
+- QVariantMap fields;
+- foreach (const QDjangoMetaField &field, d->localFields) {
+- if (field.d->name != d->primaryKey) {
+- const QVariant value = model->property(field.d->name);
+- fields.insert(field.d->name, field.toDatabase(value));
+- }
+- }
+-
+- // perform UPDATE
+- QDjangoQuerySetPrivate qs(model->metaObject()->className());
+- qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, pk));
+- return qs.sqlUpdate(fields) != -1;
+- }
+- }
+-
+- // prepare data
+- QVariantMap fields;
+- foreach (const QDjangoMetaField &field, d->localFields) {
+- if (!field.d->autoIncrement) {
+- const QVariant value = model->property(field.d->name);
+- fields.insert(field.name(), field.toDatabase(value));
+- }
+- }
+-
+- // perform INSERT
+- QVariant insertId;
+- QDjangoQuerySetPrivate qs(model->metaObject()->className());
+- if (!qs.sqlInsert(fields, &insertId))
+- return false;
+-
+- // fetch autoincrement pk
+- if (primaryKey.d->autoIncrement)
+- model->setProperty(d->primaryKey, insertId);
+- return true;
++ // find primary key
++ const QDjangoMetaField primaryKey = localField("pk");
++ const QVariant pk = model->property(d->primaryKey);
++ if (!pk.isNull() && !(primaryKey.d->type == QVariant::Int && !pk.toInt()))
++ {
++ QSqlDatabase db = QDjango::database();
++ QDjangoQuery query(db);
++ query.prepare(QString("SELECT 1 AS a FROM %1 WHERE %2 = ?").arg(
++ db.driver()->escapeIdentifier(d->table, QSqlDriver::FieldName),
++ db.driver()->escapeIdentifier(primaryKey.column(), QSqlDriver::FieldName)));
++ query.addBindValue(pk);
++ if (query.exec() && query.next())
++ {
++ // prepare data
++ QVariantMap fields;
++ foreach (const QDjangoMetaField &field, d->localFields) {
++ if (field.d->name != d->primaryKey) {
++ const QVariant value = model->property(field.d->name);
++ fields.insert(field.d->name, field.toDatabase(value));
++ }
++ }
++
++ // perform UPDATE
++ QDjangoQuerySetPrivate qs(model->metaObject()->className());
++ qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, pk));
++ return qs.sqlUpdate(fields) != -1;
++ }
++ }
++
++ // prepare data
++ QVariantMap fields;
++ foreach (const QDjangoMetaField &field, d->localFields) {
++ if (!field.d->autoIncrement) {
++ const QVariant value = model->property(field.d->name);
++ fields.insert(field.name(), field.toDatabase(value));
++ }
++ }
++
++ // perform INSERT
++ QVariant insertId;
++ QDjangoQuerySetPrivate qs(model->metaObject()->className());
++ if (!qs.sqlInsert(fields, &insertId))
++ return false;
++
++ // fetch autoincrement pk
++ if (primaryKey.d->autoIncrement)
++ model->setProperty(d->primaryKey, insertId);
++ return true;
+ }
+"""]]
+
+## The extracted whitespace changes
+
+[[!format diff """
+--- a/qdjango-0.2.6/src/db/QDjangoMetaModel.cpp
++++ b/qdjango-0.2.6/src/db/QDjangoMetaModel.cpp
+@@ -231,6 +231,13 @@ QDjangoMetaModel::QDjangoMetaModel(const QObject *model)
+ field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption;
+ field.d->index = true;
+ field.d->null = nullOption;
++ if (primaryKeyOption) {
++ field.d->autoIncrement = autoIncrementOption;
++ field.d->unique = true;
++ d->primaryKey = field.d->name;
++ } else if (uniqueOption) {
++ field.d->unique = true;
++ }
+ d->localFields << field;
+ continue;
+ }
+"""]]