new blag post: Splitting overly large hunks in patches
authorRoland Hieber <rohieb@rohieb.name>
Thu, 24 Oct 2013 16:30:29 +0000 (18:30 +0200)
committerRoland Hieber <rohieb@rohieb.name>
Thu, 24 Oct 2013 16:30:29 +0000 (18:30 +0200)
blag/post/splitting-overly-large-hunks-in-patches.mdwn [new file with mode: 0644]
blag/post/splitting-overly-large-hunks-in-patches/edit-stages.mdwn [new file with mode: 0644]

diff --git a/blag/post/splitting-overly-large-hunks-in-patches.mdwn b/blag/post/splitting-overly-large-hunks-in-patches.mdwn
new file mode 100644 (file)
index 0000000..809be3c
--- /dev/null
@@ -0,0 +1,36 @@
+[[!meta title="Splitting overly large hunks in patches"]]
+[[!meta author="rohieb"]]
+[[!meta license="CC-BY-SA 3.0"]]
+[[!img defaults size=x200]]
+
+Today I stumbled over a lengthy patch on my harddisk. It was about half a year
+old, and consisted of only one hunk, which was about 1000 lines in length. Most
+of the contents were indentation changes from tabs to spaces, but I knew that
+the patch contained a small useful portion, which I wanted to extract. What was
+slightly more annoying was the fact the the patch did not apply cleanly to the
+file it was supposed to change, and `patch` only applies hunks atomically, the
+whole patch was rejected.
+
+Since I did not want to compare each of the lines in the patch visually and
+decide whether they changed only whitespace, I tried to look for a way to split
+the patch into smaller hunks. My first try was looking at the useful tool in the
+[patchutils](http://cyberelk.net/tim/software/patchutils/) package, but none of
+them did what I wanted, they only allowed me to split patches into single hunks
+(but my patch already had only one hunk).
+
+But after a bit of googling, I found out that Emacs has a
+[`diff-split-hunk`][emacs-dsh] command, so I installed Emacs (for the first time
+in my life), opened my patch, selected Emacs' Diff mode with `M-x diff-mode`,
+and split the patch into smaller hunks by pressing `C-x C-s` on appropriate
+context lines. After saving, the patch applied cleanly except for two smaller
+hunks, which I could easily identify as containing only whitespace changes. Then
+I could compare my patched file with the original file, this time ignoring
+whitespace changes with `diff -w`, and, voilà, I got the seven useful lines I
+wanted.
+
+[emacs-dsh]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Diff-Mode.html
+
+For illustration, see the different [[edit stages of my patch|edit-stages]] on a
+separate page.
+
+[[!tag patch patchutils diff GNU Emacs QDjango C++ howto]]
diff --git a/blag/post/splitting-overly-large-hunks-in-patches/edit-stages.mdwn b/blag/post/splitting-overly-large-hunks-in-patches/edit-stages.mdwn
new file mode 100644 (file)
index 0000000..3a0fdab
--- /dev/null
@@ -0,0 +1,2087 @@
+[[!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;
+                               }
+"""]]
This page took 0.066298 seconds and 4 git commands to generate.