├── .travis.yml ├── AUTHORS ├── ChangeLog ├── LICENSE.LGPL ├── README.md ├── doc ├── Doxyfile ├── database.doc ├── http.doc ├── index.doc ├── models.doc └── queries.doc ├── examples ├── examples.pro └── http-server │ ├── base.css │ ├── http-server.cpp │ ├── http-server.h │ ├── http-server.pro │ ├── http-server.qrc │ └── templates │ ├── base.html │ ├── change_form.html │ ├── change_list.html │ ├── delete_confirmation.html │ ├── footer.html │ ├── header.html │ └── index.html ├── qdjango.pri ├── qdjango.pro ├── src ├── db │ ├── QDjango.cpp │ ├── QDjango.h │ ├── QDjangoMetaModel.cpp │ ├── QDjangoMetaModel.h │ ├── QDjangoModel.cpp │ ├── QDjangoModel.h │ ├── QDjangoQuerySet.cpp │ ├── QDjangoQuerySet.h │ ├── QDjangoQuerySet_p.h │ ├── QDjangoWhere.cpp │ ├── QDjangoWhere.h │ ├── QDjangoWhere_p.h │ ├── QDjango_p.h │ └── db.pro ├── http │ ├── QDjangoFastCgiServer.cpp │ ├── QDjangoFastCgiServer.h │ ├── QDjangoFastCgiServer_p.h │ ├── QDjangoHttpController.cpp │ ├── QDjangoHttpController.h │ ├── QDjangoHttpRequest.cpp │ ├── QDjangoHttpRequest.h │ ├── QDjangoHttpRequest_p.h │ ├── QDjangoHttpResponse.cpp │ ├── QDjangoHttpResponse.h │ ├── QDjangoHttpResponse_p.h │ ├── QDjangoHttpServer.cpp │ ├── QDjangoHttpServer.h │ ├── QDjangoHttpServer_p.h │ ├── QDjangoHttp_p.h │ ├── QDjangoUrlResolver.cpp │ ├── QDjangoUrlResolver.h │ └── http.pro ├── src.pri └── src.pro └── tests ├── db ├── auth-models.cpp ├── auth-models.h ├── auth │ ├── auth.pro │ └── tst_auth.cpp ├── db.pri ├── db.pro ├── qdjango │ ├── qdjango.pro │ └── tst_qdjango.cpp ├── qdjangocompiler │ ├── qdjangocompiler.pro │ └── tst_qdjangocompiler.cpp ├── qdjangometamodel │ ├── qdjangometamodel.pro │ ├── tst_qdjangometamodel.cpp │ └── tst_qdjangometamodel.h ├── qdjangomodel │ ├── qdjangomodel.pro │ └── tst_qdjangomodel.cpp ├── qdjangoqueryset │ ├── qdjangoqueryset.pro │ └── tst_qdjangoqueryset.cpp ├── qdjangowhere │ ├── qdjangowhere.pro │ └── tst_qdjangowhere.cpp ├── shares │ ├── shares.pro │ └── tst_shares.cpp ├── util.cpp └── util.h ├── http ├── http.pri ├── http.pro ├── qdjangofastcgiserver │ ├── qdjangofastcgiserver.pro │ └── tst_qdjangofastcgiserver.cpp ├── qdjangohttpcontroller │ ├── qdjangohttpcontroller.pro │ ├── test.bin │ ├── test.css │ ├── test.html │ ├── test.js │ ├── tst_qdjangohttpcontroller.cpp │ └── tst_qdjangohttpcontroller.qrc ├── qdjangohttprequest │ ├── qdjangohttprequest.pro │ └── tst_qdjangohttprequest.cpp ├── qdjangohttpresponse │ ├── qdjangohttpresponse.pro │ └── tst_qdjangohttpresponse.cpp ├── qdjangohttpserver │ ├── qdjangohttpserver.pro │ └── tst_qdjangohttpserver.cpp └── qdjangourlresolver │ ├── qdjangourlresolver.pro │ └── tst_qdjangourlresolver.cpp ├── tests.pri ├── tests.pro └── travis └── build-and-test /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: cpp 3 | cache: apt 4 | addons: 5 | apt: 6 | packages: 7 | - lcov 8 | - libmyodbc 9 | - libqt4-dev 10 | - libqt4-sql-mysql 11 | - libqt4-sql-odbc 12 | - libqt4-sql-psql 13 | - libqt4-sql-sqlite 14 | - libqt5sql5-mysql 15 | - libqt5sql5-odbc 16 | - libqt5sql5-psql 17 | - libqt5sql5-sqlite 18 | - odbc-postgresql 19 | - odbcinst 20 | - qtbase5-dev 21 | services: 22 | - mysql 23 | - postgresql 24 | env: 25 | - QT_SELECT=qt4 CONFIG=shared 26 | - QT_SELECT=qt4 CONFIG=static 27 | - QT_SELECT=qt5 CONFIG=shared 28 | - QT_SELECT=qt5 CONFIG=static 29 | before_script: 30 | - mysql -e 'create database qdjango_test' -u root 31 | - psql -c 'create database qdjango_test' -U postgres 32 | script: 33 | - tests/travis/build-and-test 34 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Jeremy Lainé 2 | * Principal developer of QDjango. 3 | 4 | Mathias Hasselmann 5 | * Support for QDjangoQuerySet iterators. 6 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | QDjango 0.6.2 (2015-10-22) 2 | * Make sure HTTP dates are handled using C locale. 3 | 4 | QDjango 0.6.1 (2015-08-13) 5 | * Bump QDJANGO_VERSION, which had stayed at 0.5.0. 6 | 7 | QDjango 0.6.0 (2015-08-12) 8 | * Remove support for QtScript. 9 | * Make tests runnable using "make check". 10 | * Fix build errors on Windows. 11 | * Clarify return values of createTable(s) and dropTable(s). 12 | * Fix documentation for setForeignKey concerning object ownership. 13 | 14 | QDjango 0.5.0 (2014-09-13) 15 | * Add support for case-insensitive lookups. 16 | * Simplify SQL for multiple OR/AND clauses. 17 | * Add support for reverse lookups for many-to-one relations. 18 | * Add support QtSql's ODBC driver. 19 | * Add support for Microsoft SQL Server. 20 | * Improve test coverage. 21 | 22 | QDjango 0.4.0 (2013-06-27) 23 | * Enable foreign key constraints on SQLite (issue #9). 24 | * Fix table creation with foreign key constraints on PostgreSQL (issue #10). 25 | * Expose QDjangoMetaField's properties (issue #13). 26 | * Fix http module build with Qt5. 27 | 28 | QDjango 0.3.0 (2013-01-11) 29 | * Fix and test "unique" field option. 30 | * Add a "unique_together" model option. 31 | * Don't crash if QDjango::database() is called before QDjango::setDatabase(). 32 | * Make index names coincide with those used by django. 33 | * Register models without instantiating them. 34 | * Improve foreign keys: 35 | - Do not take object ownership in QDjangoMetaModel::setForeignKey(). 36 | - Fix QDjangoQuerySet::selectRelated() with NULL foreign keys. 37 | 38 | QDjango 0.2.6 (2012-09-07) 39 | * Store 0 integer foreign keys as NULL if field has null option. 40 | * Make it possible to build with Qt5: 41 | - Stop using deprecated QHttpRequestHeader and QHttpResponseHeader classes. 42 | - Use QMetaMethod::name() instead of QMetaMethod::signature() when using Qt5. 43 | 44 | QDjango 0.2.5 (2012-05-16) 45 | * Build a shared library by default. 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | QDjango - a Qt-based C++ web framework 2 | Copyright (c) 2010-2015 Jeremy Lainé 3 | 4 | [![Build Status](https://travis-ci.org/jlaine/qdjango.png)](https://travis-ci.org/jlaine/qdjango) 5 | 6 | About 7 | ===== 8 | 9 | _QDjango_ is a web framework written in C++ and built on top of the Qt library. 10 | Where possible it tries to follow django's API, hence its name. 11 | 12 | It is released under the terms of the GNU Lesser General Public License, version 2.1 or later. 13 | 14 | **This project is no longer maintained.** 15 | 16 | Requirements 17 | ============ 18 | 19 | QDjango builds and is auto-tested both with Qt 4 and Qt 5. 20 | 21 | Qt 4 on Debian: 22 | 23 | sudo apt-get install libqt4-dev 24 | 25 | Qt 5 on Debian: 26 | 27 | sudo apt-get install qtbase5-dev 28 | 29 | Qt 4 on Mac OS X: 30 | 31 | sudo port install qt4-mac 32 | 33 | Building QDjango 34 | ================ 35 | 36 | mkdir build 37 | cd build 38 | qmake .. 39 | make 40 | 41 | You can pass the following arguments to qmake: 42 | 43 | PREFIX= to change the install prefix 44 | default: 45 | unix: /usr/local on unix 46 | other: $$[QT_INSTALL_PREFIX] 47 | QDJANGO_LIBRARY_TYPE=staticlib to build a static version of QDjango 48 | 49 | Notes 50 | ====== 51 | 52 | MSSQL 53 | ----- 54 | 55 | Fast forward cursors are used by default. This greatly improves performance, and has the added benefit of implicitly converting to a static cursor when it [needs to]( http://technet.microsoft.com/en-us/library/aa172573(v=sql.80).aspx). Unfortunately, this also means that these cursors can block a connection to the server. In order to deal properly with this situation, there are a few requirements: 56 | 57 | - Connection pooling must be enabled in your [ODBC manager](http://www.unixodbc.org/doc/conn_pool.html) 58 | - You must enable Multiple Active Result Sets in the QODBC driver using "MARS_Connection=Yes" in the connection string 59 | - You must enable connection pooling in the QODBC driver using the "SQL_ATTR_CONNECTION_POOLING" attribute 60 | -------------------------------------------------------------------------------- /doc/database.doc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | /*! 19 | 20 | \defgroup Database 21 | 22 | \brief Object Relation Mapper (ORM) 23 | 24 | QDjango's Object Relation Mapper (ORM) strives to be both powerful 25 | and simple to use. Where possible it tries to follow django's 26 | ORM API, with a similar lazy queryset mechanism. 27 | 28 | The object relation mapper builds upon Qt's Meta-Object System, 29 | so if you are familiar with Qt, you should feel right at home. Features include: 30 | 31 | \li support for a wide range of database backends thanks to QtSql 32 | \li creating and registering database models is easy using Qt's property system 33 | \li performing queries using querysets 34 | \li QDjango can create and drop database tables and indices for registered models 35 | \li thread-aware access to the database 36 | 37 | \page database Database configuration 38 | 39 | \section setup Setting the database 40 | 41 | The first step is to open the database using 42 | QSqlDatabase::addDatabase(), 43 | for instance for an in-memory SQLite database: 44 | 45 | \code 46 | QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); 47 | db.setDatabaseName(":memory:"); 48 | db.open(); 49 | \endcode 50 | 51 | You should now tell QDjango to use the database you just opened: 52 | 53 | \code 54 | QDjango::setDatabase(db); 55 | \endcode 56 | 57 | \section creating Creating or dropping database tables 58 | 59 | Once you have set the database and declared all your models (see \ref models), you can ask QDjango to create 60 | the database tables for all models: 61 | 62 | \code 63 | QDjango::createTables(); 64 | \endcode 65 | 66 | Conversely, you can ask QDjango to drop the database tables for all models: 67 | 68 | \code 69 | QDjango::dropTables(); 70 | \endcode 71 | 72 | \section threading Threading support 73 | 74 | Internally, QDjango calls the QDjango::database() method whenever it needs a handle to the database. This method will clone the database connection as needed if it is invoked from a different thread. 75 | 76 | */ 77 | -------------------------------------------------------------------------------- /doc/http.doc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | /*! 19 | 20 | \defgroup Http 21 | 22 | \brief HTTP request and response framework 23 | 24 | QDjango's HTTP request and response framework enables you to write web 25 | web applications and deploy them as FastCGI applications or standalone 26 | HTTP servers. 27 | 28 | */ 29 | -------------------------------------------------------------------------------- /doc/index.doc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | /*! 19 | \mainpage 20 | 21 | QDjango is a web framework written in C++ and built on top of the Qt library (versions 4 or 5). 22 | 23 | Where possible it tries to follow django's API, hence its name. 24 | 25 | It is released under the terms of the GNU Lesser General Public License, version 2.1 or later. 26 | 27 | \sa Database 28 | \sa Http 29 | */ 30 | -------------------------------------------------------------------------------- /doc/models.doc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | /*! \page models Database models 19 | 20 | Database models are usually created by subclassing the QDjangoModel class. 21 | 22 | The following example defines a \c User model suitable for storing basic 23 | account information, and illustrate different types of queries on this model. 24 | 25 | \section declaring Declaring your model 26 | 27 | To declare your model, subclass the QDjangoModel class, and define a property 28 | using the Q_PROPERTY 29 | macro for each database field. You can provide additional information about a 30 | field using the Q_CLASSINFO 31 | macro: 32 | 33 | \li \c max_length : the maximum length of the field (used when creating the database table) 34 | \li \c primary_key : if set to 'true', this field will be used as the primary key. If no primary key is explicitly defined, an auto-increment integer field will be added. 35 | 36 | \code 37 | #include "QDjangoModel.h" 38 | 39 | class User : public QDjangoModel 40 | { 41 | Q_OBJECT 42 | Q_PROPERTY(QString username READ username WRITE setUsername) 43 | Q_PROPERTY(QString password READ password WRITE setPassword) 44 | 45 | Q_CLASSINFO("username", "max_length=255") 46 | Q_CLASSINFO("password", "max_length=128") 47 | 48 | public: 49 | QString username() const; 50 | void setUsername(const QString &username); 51 | 52 | QString password() const; 53 | void setPassword(const QString &password); 54 | 55 | private: 56 | QString m_username; 57 | QString m_password; 58 | }; 59 | \endcode 60 | 61 | \section implementing Implementing your model 62 | 63 | \code 64 | QString User::username() const 65 | { 66 | return m_username; 67 | } 68 | 69 | void User::setUsername(const QString &username) 70 | { 71 | m_username = username; 72 | } 73 | 74 | QString User::password() const 75 | { 76 | return m_password; 77 | } 78 | 79 | void User::setPassword(const QString &password) 80 | { 81 | m_password = password; 82 | } 83 | \endcode 84 | 85 | \section registering Registering and using your model 86 | 87 | To make your model available for database operations, you should now register your model using: 88 | 89 | \code 90 | QDjango::registerModel(); 91 | \endcode 92 | 93 | Once you have set the database (see \ref database), you will now be able to create model instances and save them to the database: 94 | 95 | \code 96 | User *user = new User; 97 | user->setUsername("someuser"); 98 | user->setPassword("somepassword"); 99 | user->save(); 100 | \endcode 101 | 102 | .. or remove them from the database: 103 | 104 | \code 105 | user->remove(); 106 | \endcode 107 | 108 | You can also perform operations such as filtering or retrieving model 109 | instances as described in \ref queries. 110 | 111 | \section qobject Using QDjango without QDjangoModel 112 | 113 | Although it is recommended you make your models inherit QDjangoModel, it is not strictly necessary. QDjango can in fact handle any QObject-derived class, but you will lose some of the syntactic sugar. 114 | 115 | If for instance you defined a \c SomeObject class which inherits QObject, you can write: 116 | 117 | \code 118 | QDjangoMetaModel meta = QDjango::registerModel(); 119 | 120 | // prepare a SomeObject instance 121 | SomeObject *obj = new SomeObject; 122 | obj->setSomeProperty("some value"); 123 | obj->setOtherProperty("other value"); 124 | 125 | // save the object 126 | meta.save(obj); 127 | 128 | // remove the object from database 129 | meta.remove(obj); 130 | \endcode 131 | 132 | */ 133 | -------------------------------------------------------------------------------- /doc/queries.doc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | /*! \page queries Making queries 19 | 20 | The QDjango object relational mapper (ORM) supports the concept of querysets, borrowed from django's ORM. A queryset is a collection of database objects which match a certain number of user-specified conditions. 21 | 22 | You can learn more about querysets by reading the QDjangoQuerySet template class documentation. 23 | 24 | \section creating-queries Creating and filtering querysets 25 | 26 | Before you can start using querysets, you need to declare your database models as described in \ref models. 27 | 28 | The most basic queryset matches all the objects for a given model. 29 | 30 | \code 31 | // all users 32 | QDjangoQuerySet users; 33 | \endcode 34 | 35 | You can use the QDjangoQuerySet::filter() and QDjangoQuerySet::exclude() methods to add filtering conditions to a querset: 36 | 37 | \code 38 | // find all users whose password is "foo" and whose username is not "bar" 39 | QDjangoQuerySet someUsers; 40 | someUsers = users.filter(QDjangoWhere("password", QDjangoWhere::Equals, "foo") && 41 | QDjangoWhere("username", QDjangoWhere::NotEquals, "bar")); 42 | 43 | // find all users whose username is "foo" or "bar" 44 | someUsers = users.filter(QDjangoWhere("username", QDjangoWhere::Equals, "foo") || 45 | QDjangoWhere("username", QDjangoWhere::Equals, "bar")); 46 | 47 | // find all users whose username starts with "f": 48 | someUsers = users.filter(QDjangoWhere("username", QDjangoWhere::StartsWith, "f")); 49 | \endcode 50 | 51 | You can also use the QDjangoQuerySet::limit() method to limit the number of returned rows: 52 | 53 | \code 54 | // limit number of results 55 | someUsers = users.limit(0, 100); 56 | \endcode 57 | 58 | \section iterating-queries Iterating over results 59 | 60 | The easiest way to iterate over results is to use Qt's foreach keyword: 61 | 62 | \code 63 | // iterate over matching users 64 | foreach (const User &user, someUsers) { 65 | qDebug() << "found user" << user.username; 66 | } 67 | \endcode 68 | 69 | Another way of iterating over results is to run over model instances using the QDjangoQuerySet::size() and QDjangoQuerySet::at() methods: 70 | 71 | \code 72 | // iterate over matching users 73 | User user; 74 | for (int i = 0; i < someUsers.size(); ++i) { 75 | if (someUsers.at(i, &user)) { 76 | qDebug() << "found user" << user.username; 77 | } 78 | } 79 | \endcode 80 | 81 | It is also possible to retrieve field data without creating model instances using the QDjangoQuerySet::values() and QDjangoQuerySet::valuesList() methods: 82 | 83 | \code 84 | // retrieve usernames and passwords for matching users as maps 85 | QList propertyMaps = someUsers.values(QStringList() << "username" << "password"); 86 | foreach (const QVariantMap &propertyMap, propertyMaps) { 87 | qDebug() << "username" << propertyList["username"]; 88 | qDebug() << "password" << propertyList["password"]; 89 | } 90 | 91 | // retrieve usernames and passwords for matching users as lists 92 | QList propertyLists = someUsers.valuesList(QStringList() << "username" << "password"); 93 | foreach (const QVariantList &propertyList, propertyLists) { 94 | qDebug() << "username" << propertyList[0]; 95 | qDebug() << "password" << propertyList[1]; 96 | } 97 | \endcode 98 | 99 | \section other-queries Other operations 100 | 101 | \code 102 | // count matching users without retrieving their data 103 | int numberOfUsers = someUsers.count(); 104 | 105 | // delete all the users in the queryset 106 | someUsers.remove(); 107 | \endcode 108 | 109 | \section select-related Selecting related objects 110 | It is efficient to fetch the main object together with the selected one. 111 | This is particularly true if multiple objects are needed so that all of them are fetched with a single query. 112 | QDjagno offers two ways how to select related objects. 113 | The default option is to traverse all foreign keys recursuvely. 114 | In case of small dependence trees this option is suitable, however when the database structure is complex, fetching all related object in a recursive manner can be overwhelming. 115 | Therefore, the QDjangoQuerySet::selectRelated() method can be invoked with a list of foreign keys that have to be traversed, thus limiting the set of fetched related objects. 116 | 117 | 118 | Let us have the following model structure: 119 | \code 120 | #include 121 | #include 122 | #include 123 | #include 124 | #include 125 | #include 126 | #include 127 | class Author : public QDjangoModel 128 | { Q_OBJECT 129 | Q_PROPERTY(long author_id READ author_id WRITE set_author_id) 130 | Q_CLASSINFO("author_id","primary_key=true auto_increment=true") 131 | Q_PROPERTY(QString name READ name WRITE set_name) 132 | Q_CLASSINFO("name","max_length=254 null=true blank=true") 133 | Q_PROPERTY(QDateTime birthday READ birthday WRITE set_birthday) 134 | Q_CLASSINFO("birthday","null=true blank=true") 135 | public: 136 | Author(QObject *parent=0); 137 | long author_id() const; 138 | void set_author_id(long value); 139 | QString name() const; 140 | QDateTime birthday() const; 141 | void set_birthday(QDateTime value); 142 | private: 143 | long m_author_id; 144 | QString m_name; 145 | QDateTime m_birthday; 146 | }; 147 | 148 | class Location : public QDjangoModel 149 | { Q_OBJECT 150 | Q_PROPERTY(long location_id READ publisher_id WRITE set_location_id) 151 | Q_CLASSINFO("location_id","primary_key=true auto_increment=true") 152 | Q_PROPERTY(QString name READ name WRITE set_name) 153 | Q_CLASSINFO("name","max_length=254 null=true blank=true") 154 | public: 155 | Location(QObject *parent=0); 156 | long location_id() const; 157 | void set_location_id(long value); 158 | QString name() const; 159 | private: 160 | long m_location_id; 161 | QString m_name; 162 | }; 163 | 164 | class Publisher : public QDjangoModel 165 | { Q_OBJECT 166 | Q_PROPERTY(long publisher_id READ publisher_id WRITE set_publisher_id) 167 | Q_CLASSINFO("author_id","primary_key=true auto_increment=true") 168 | Q_PROPERTY(QString name READ name WRITE set_name) 169 | Q_CLASSINFO("name","max_length=254 null=true blank=true") 170 | Q_PROPERTY(Location* location READ location WRITE set_location) 171 | public: 172 | Publisher(QObject *parent=0); 173 | long publisher_id() const; 174 | void set_publisher_id(long value); 175 | QString name() const; 176 | void sel_location(Location *value); 177 | Location* location(); 178 | private: 179 | long m_publisher_id; 180 | QString m_name; 181 | }; 182 | 183 | class Book : public QDjangoModel 184 | { Q_OBJECT 185 | Q_PROPERTY(long book_id READ book_id WRITE set_book_id) 186 | Q_CLASSINFO("book_id","primary_key=true auto_increment=true") 187 | Q_PROPERTY(QString name READ name WRITE set_name) 188 | Q_CLASSINFO("name","max_length=254 null=true blank=true") 189 | Q_PROPERTY(Author* author READ author WRITE set_author) 190 | Q_PROPERTY(Publisher* publisher READ publisher WRITE set_publisher) 191 | public: 192 | Book(QObject *parent=0); 193 | long book_id() const; 194 | void set_book_id(long value); 195 | QString name() const; 196 | void set_author(Author* value); 197 | Author* author(); 198 | void set_publisher(Publisher* value); 199 | Publisher* publisher(); 200 | private: 201 | long m_book_id; 202 | QString m_name; 203 | }; 204 | \endcode 205 | 206 | If one invokes QDjangoQuerySet::selectRelated() without arguments on a QuerySet of Book, QDjango will return the selected Book object with filled Author, Publisher as well as Location of the Publisher. 207 | In order to limit the pre-fetched objects, just for Author the QDjangoQuerySet::selectRelated() method should be invoked using a QStringList in the following format 208 | \code 209 | QStringList relatedNames; 210 | relatedNames << "author" << "publisher"; 211 | \endcode 212 | 213 | Limiting the pre-fetching to publisher and its location the QStringList becomes: 214 | \code 215 | QStringList relatedNames; 216 | relatedNames << "publisher__location"; 217 | \endcode 218 | 219 | Fetching all the complete structure as if the method was invoked without any argument is equivalent to the following QStringList: 220 | \code 221 | QStringList relatedNames; 222 | relatedNames << "publisher__location" << "author"; 223 | \endcode 224 | 225 | The first level of the related names represent the foreign keys of the class Book. The subsequent foreign keys of the related objects are separated by double underscore '__' as in the QDjangoWhere class. 226 | */ 227 | -------------------------------------------------------------------------------- /examples/examples.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS = http-server 3 | -------------------------------------------------------------------------------- /examples/http-server/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; 3 | font-size 12px; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | a:link, a:visited { 9 | color: #5B80B2; 10 | text-decoration: none; 11 | } 12 | 13 | h1 { 14 | color: #666666; 15 | font-size: 18px; 16 | margin: 0 0 0.2em; 17 | padding: 0 6px 0 0; 18 | } 19 | 20 | input[type="text"], input[type="password"], textarea, select { 21 | border: 1px solid #CCCCCC; 22 | } 23 | 24 | table { 25 | border-collapse: collapse; 26 | } 27 | 28 | td, th { 29 | font-size: 11px; 30 | line-height: 13px; 31 | border-bottom: 1px solid #eee; 32 | vertical-align: top; 33 | padding: 5px; 34 | text-align: left; 35 | } 36 | 37 | th { 38 | color: #666; 39 | padding: 2px 5px; 40 | font-weight: bold; 41 | background: #e1e1e1; 42 | background-image: linear-gradient(bottom, rgb(255,255,255) 10%, rgb(245,245,245) 100%); 43 | background-image: -moz-linear-gradient(top, rgb(255,255,255) 10%, rgb(245,245,245) 100%); 44 | background-image: -webkit-linear-gradient(top, rgb(255,255,255) 10%, rgb(245,245,245) 100%); 45 | border-left: 1px solid #ddd; 46 | border-bottom: 1px solid #ddd; 47 | } 48 | 49 | #header { 50 | background: none repeat scroll 0 0 #417690; 51 | color: #FFFFCC; 52 | overflow: hidden; 53 | } 54 | 55 | #header h1 { 56 | color: #F4F379; 57 | font-size: 18px; 58 | font-weight: normal; 59 | margin: 8px 0; 60 | padding: 0 10px; 61 | } 62 | 63 | #content { 64 | margin: 10px 15px; 65 | } 66 | 67 | .breadcrumbs { 68 | background: white; 69 | border-bottom: 1px solid #CCCCCC; 70 | border-top: 1px solid white; 71 | color: #999999; 72 | font-size: 11px; 73 | padding: 2px 8px 3px; 74 | text-align: left; 75 | } 76 | 77 | .module { 78 | background: none repeat scroll 0 0 white; 79 | border: 1px solid #CCCCCC; 80 | margin-bottom: 5px; 81 | } 82 | 83 | .aligned label { 84 | display: inline-block; 85 | padding: 3px 10px 0 0; 86 | width: 8em; 87 | } 88 | -------------------------------------------------------------------------------- /examples/http-server/http-server.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | 20 | class QDjangoHttpRequest; 21 | class QDjangoHttpResponse; 22 | class QDjangoUrlResolver; 23 | class AdminControllerPrivate; 24 | class ModelAdminFetcher; 25 | class ModelAdminPrivate; 26 | 27 | class AdminController : public QObject 28 | { 29 | Q_OBJECT 30 | 31 | public: 32 | AdminController(QObject* parent = 0); 33 | void setupUrls(QDjangoUrlResolver *urls); 34 | 35 | public slots: 36 | QDjangoHttpResponse* index(const QDjangoHttpRequest &request); 37 | QDjangoHttpResponse* staticFiles(const QDjangoHttpRequest &request, const QString &path); 38 | 39 | private: 40 | AdminControllerPrivate *d; 41 | }; 42 | 43 | class ModelAdmin : public QObject 44 | { 45 | Q_OBJECT 46 | 47 | public: 48 | ModelAdmin(ModelAdminFetcher *fetcher, QObject *parent = 0); 49 | ~ModelAdmin(); 50 | 51 | QList changeFields() const; 52 | void setChangeFields(const QList fields); 53 | 54 | QList listFields() const; 55 | void setListFields(const QList fields); 56 | 57 | QDjangoUrlResolver *urls() const; 58 | 59 | public slots: 60 | QDjangoHttpResponse* addForm(const QDjangoHttpRequest &request); 61 | QDjangoHttpResponse* changeForm(const QDjangoHttpRequest &request, const QString &objectId); 62 | QDjangoHttpResponse* changeList(const QDjangoHttpRequest &request); 63 | QDjangoHttpResponse* deleteForm(const QDjangoHttpRequest &request, const QString &objectId); 64 | 65 | private: 66 | ModelAdminPrivate *d; 67 | }; 68 | 69 | -------------------------------------------------------------------------------- /examples/http-server/http-server.pro: -------------------------------------------------------------------------------- 1 | include(../../qdjango.pri) 2 | 3 | QT += network sql 4 | 5 | TARGET = qdjango-http-server 6 | 7 | INCLUDEPATH += ../../tests/db $$QDJANGO_INCLUDEPATH 8 | LIBS += \ 9 | -L../../src/db $$QDJANGO_DB_LIBS \ 10 | -L../../src/http $$QDJANGO_HTTP_LIBS 11 | RESOURCES += http-server.qrc 12 | HEADERS += http-server.h ../../tests/db/auth-models.h 13 | SOURCES += http-server.cpp ../../tests/db/auth-models.cpp 14 | -------------------------------------------------------------------------------- /examples/http-server/http-server.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | base.css 5 | templates/change_form.html 6 | templates/change_list.html 7 | templates/delete_confirmation.html 8 | templates/footer.html 9 | templates/header.html 10 | templates/index.html 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/http-server/templates/base.html: -------------------------------------------------------------------------------- 1 | {% include "header.html" %} 2 | {% include "footer.html" %} 3 | -------------------------------------------------------------------------------- /examples/http-server/templates/change_form.html: -------------------------------------------------------------------------------- 1 | {% include "header.html" %} 2 |
3 |
4 | {% for field in field_list %} 5 |
6 | 7 | 8 |
9 | {% endfor %} 10 |
11 |
12 | Delete 13 | 14 |
15 |
16 | {% include "footer.html" %} 17 | -------------------------------------------------------------------------------- /examples/http-server/templates/change_list.html: -------------------------------------------------------------------------------- 1 | {% include "header.html" %} 2 | 5 | 6 |
7 | 8 | 9 | {% for field in field_list %} 10 | 11 | {% endfor %} 12 | 13 | 14 | {% for object in object_list %} 15 | 16 | {% for value in object.value_list %} 17 | 18 | {% endfor %} 19 | 23 | 24 | {% endfor %} 25 |
{{ field.name }}Actions
{% if forloop.counter == "1" %}{{ value }}{% else %}{{ value }}{% endif %} 20 | {{ edit_link }} 21 | {{ delete_link }} 22 |
26 |
27 | {% include "footer.html" %} 28 | -------------------------------------------------------------------------------- /examples/http-server/templates/delete_confirmation.html: -------------------------------------------------------------------------------- 1 | {% include "header.html" %} 2 |

3 | Are you sure you want to delete the {{ model_name }} "{{ original.username }}"? 4 |

5 |
6 | 7 |
8 | {% include "footer.html" %} 9 | -------------------------------------------------------------------------------- /examples/http-server/templates/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/http-server/templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title }} | Test application 6 | {% comment %} 7 | test 8 | {% endcomment %} 9 | 10 | 11 |
12 | 15 |
16 | 29 |
30 |

{{ title }}

31 | -------------------------------------------------------------------------------- /examples/http-server/templates/index.html: -------------------------------------------------------------------------------- 1 | {% include "header.html" %} 2 |
3 | 4 | 5 | {% for model in model_list %} 6 | 7 | 8 | 12 | 13 | {% endfor %} 14 | 15 |
{{ model }} 9 | Add 10 | Change 11 |
16 |
17 | {% include "footer.html" %} 18 | -------------------------------------------------------------------------------- /qdjango.pri: -------------------------------------------------------------------------------- 1 | # Common definitions 2 | QDJANGO_VERSION=0.6.2 3 | 4 | # Determine library type (shared or staticlib) 5 | isEmpty(QDJANGO_LIBRARY_TYPE) { 6 | android { 7 | QDJANGO_LIBRARY_TYPE = staticlib 8 | } else { 9 | QDJANGO_LIBRARY_TYPE = shared 10 | } 11 | } 12 | 13 | # Libraries for apps which use QDjango 14 | QDJANGO_INCLUDEPATH = $$PWD/src/db $$PWD/src/http 15 | QDJANGO_DB_LIBS = -lqdjango-db 16 | QDJANGO_HTTP_LIBS = -lqdjango-http 17 | contains(QDJANGO_LIBRARY_TYPE,staticlib) { 18 | DEFINES += QDJANGO_STATIC 19 | } else { 20 | # Windows needs the major library version 21 | win32 { 22 | QDJANGO_DB_LIBS = -lqdjango-db0 23 | QDJANGO_HTTP_LIBS = -lqdjango-http0 24 | } 25 | DEFINES += QDJANGO_SHARED 26 | } 27 | 28 | # Installation prefix and library directory 29 | isEmpty(PREFIX) { 30 | contains(MEEGO_EDITION,harmattan) { 31 | PREFIX = /usr 32 | } else:unix { 33 | PREFIX = /usr/local 34 | } else { 35 | PREFIX = $$[QT_INSTALL_PREFIX] 36 | } 37 | } 38 | isEmpty(LIBDIR) { 39 | LIBDIR=lib 40 | } 41 | -------------------------------------------------------------------------------- /qdjango.pro: -------------------------------------------------------------------------------- 1 | include(qdjango.pri) 2 | 3 | TEMPLATE = subdirs 4 | 5 | SUBDIRS = src 6 | 7 | android { 8 | } else { 9 | SUBDIRS += tests examples 10 | INSTALLS += htmldocs 11 | } 12 | 13 | CONFIG += ordered 14 | 15 | # Documentation generation 16 | docs.commands = cd doc/ && doxygen 17 | 18 | # Source distribution 19 | QDJANGO_ARCHIVE = qdjango-$$QDJANGO_VERSION 20 | dist.commands = \ 21 | $(DEL_FILE) -r $$QDJANGO_ARCHIVE && \ 22 | $(MKDIR) $$QDJANGO_ARCHIVE && \ 23 | git archive master | tar -x -C $$QDJANGO_ARCHIVE && \ 24 | $(COPY_DIR) doc/html $$QDJANGO_ARCHIVE/doc && \ 25 | tar czf $${QDJANGO_ARCHIVE}.tar.gz $$QDJANGO_ARCHIVE && \ 26 | $(DEL_FILE) -r $$QDJANGO_ARCHIVE 27 | dist.depends = docs 28 | 29 | # Install rules 30 | htmldocs.files = doc/html 31 | htmldocs.path = $$PREFIX/share/doc/qdjango 32 | htmldocs.CONFIG += no_check_exist directory 33 | 34 | QMAKE_EXTRA_TARGETS += dist docs 35 | -------------------------------------------------------------------------------- /src/db/QDjango.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "QDjango.h" 29 | 30 | static const char *connectionPrefix = "_qdjango_"; 31 | 32 | QMap globalMetaModels = QMap(); 33 | static QDjangoDatabase *globalDatabase = 0; 34 | static QDjangoDatabase::DatabaseType globalDatabaseType = QDjangoDatabase::UnknownDB; 35 | static bool globalDebugEnabled = false; 36 | 37 | /// \cond 38 | 39 | QDjangoDatabase::QDjangoDatabase(QObject *parent) 40 | : QObject(parent), connectionId(0) 41 | { 42 | } 43 | 44 | void QDjangoDatabase::threadFinished() 45 | { 46 | QThread *thread = qobject_cast(sender()); 47 | if (!thread) 48 | return; 49 | 50 | // cleanup database connection for the thread 51 | QMutexLocker locker(&mutex); 52 | disconnect(thread, SIGNAL(finished()), this, SLOT(threadFinished())); 53 | const QString connectionName = copies.value(thread).connectionName(); 54 | copies.remove(thread); 55 | if (connectionName.startsWith(QLatin1String(connectionPrefix))) 56 | QSqlDatabase::removeDatabase(connectionName); 57 | } 58 | 59 | static void closeDatabase() 60 | { 61 | delete globalDatabase; 62 | globalDatabase = 0; 63 | } 64 | 65 | static QDjangoDatabase::DatabaseType getDatabaseType(QSqlDatabase &db) 66 | { 67 | const QString driverName = db.driverName(); 68 | if (driverName == QLatin1String("QMYSQL") || 69 | driverName == QLatin1String("QMYSQL3")) 70 | return QDjangoDatabase::MySqlServer; 71 | else if (driverName == QLatin1String("QSQLITE") || 72 | driverName == QLatin1String("QSQLITE2")) 73 | return QDjangoDatabase::SQLite; 74 | else if (driverName == QLatin1String("QPSQL")) 75 | return QDjangoDatabase::PostgreSQL; 76 | else if (driverName == QLatin1String("QODBC")) { 77 | QSqlQuery query(db); 78 | if (query.exec("SELECT sqlite_version()")) 79 | return QDjangoDatabase::SQLite; 80 | 81 | if (query.exec("SELECT @@version") && query.next() && 82 | query.value(0).toString().contains("Microsoft SQL Server")) 83 | return QDjangoDatabase::MSSqlServer; 84 | 85 | if (query.exec("SELECT version()") && query.next()) { 86 | if (query.value(0).toString().contains("PostgreSQL")) 87 | return QDjangoDatabase::PostgreSQL; 88 | else 89 | return QDjangoDatabase::MySqlServer; 90 | } 91 | } 92 | return QDjangoDatabase::UnknownDB; 93 | } 94 | 95 | static void initDatabase(QSqlDatabase db) 96 | { 97 | QDjangoDatabase::DatabaseType databaseType = QDjangoDatabase::databaseType(db); 98 | if (databaseType == QDjangoDatabase::SQLite) { 99 | // enable foreign key constraint handling 100 | QDjangoQuery query(db); 101 | query.prepare("PRAGMA foreign_keys=on"); 102 | query.exec(); 103 | } 104 | } 105 | 106 | QDjangoQuery::QDjangoQuery(QSqlDatabase db) 107 | : QSqlQuery(db) 108 | { 109 | if (QDjangoDatabase::databaseType(db) == QDjangoDatabase::MSSqlServer) { 110 | // default to fast-forward cursor 111 | setForwardOnly(true); 112 | } 113 | } 114 | 115 | void QDjangoQuery::addBindValue(const QVariant &val, QSql::ParamType paramType) 116 | { 117 | // this hack is required so that we do not store a mix of local 118 | // and UTC times 119 | if (val.type() == QVariant::DateTime) 120 | QSqlQuery::addBindValue(val.toDateTime().toLocalTime(), paramType); 121 | else 122 | QSqlQuery::addBindValue(val, paramType); 123 | } 124 | 125 | bool QDjangoQuery::exec() 126 | { 127 | if (globalDebugEnabled) { 128 | qDebug() << "SQL query" << lastQuery(); 129 | QMapIterator i(boundValues()); 130 | while (i.hasNext()) { 131 | i.next(); 132 | qDebug() << "SQL " << i.key().toLatin1().data() << "=" 133 | << i.value().toString().toLatin1().data(); 134 | } 135 | } 136 | if (!QSqlQuery::exec()) { 137 | if (globalDebugEnabled) 138 | qWarning() << "SQL error" << lastError(); 139 | return false; 140 | } 141 | return true; 142 | } 143 | 144 | bool QDjangoQuery::exec(const QString &query) 145 | { 146 | if (globalDebugEnabled) 147 | qDebug() << "SQL query" << query; 148 | if (!QSqlQuery::exec(query)) { 149 | if (globalDebugEnabled) 150 | qWarning() << "SQL error" << lastError(); 151 | return false; 152 | } 153 | return true; 154 | } 155 | 156 | /// \endcond 157 | 158 | /*! 159 | Returns the database used by QDjango. 160 | 161 | If you call this method from any thread but the application's main thread, 162 | a new connection to the database will be created. The connection will 163 | automatically be torn down once the thread finishes. 164 | 165 | \sa setDatabase() 166 | */ 167 | QSqlDatabase QDjango::database() 168 | { 169 | if (!globalDatabase) 170 | return QSqlDatabase(); 171 | 172 | // if we are in the main thread, return reference connection 173 | QThread *thread = QThread::currentThread(); 174 | if (thread == globalDatabase->thread()) 175 | return globalDatabase->reference; 176 | 177 | // if we have a connection for this thread, return it 178 | QMutexLocker locker(&globalDatabase->mutex); 179 | if (globalDatabase->copies.contains(thread)) 180 | return globalDatabase->copies[thread]; 181 | 182 | // create a new connection for this thread 183 | QObject::connect(thread, SIGNAL(finished()), globalDatabase, SLOT(threadFinished())); 184 | QSqlDatabase db = QSqlDatabase::cloneDatabase(globalDatabase->reference, 185 | QLatin1String(connectionPrefix) + QString::number(globalDatabase->connectionId++)); 186 | db.open(); 187 | initDatabase(db); 188 | globalDatabase->copies.insert(thread, db); 189 | return db; 190 | } 191 | 192 | /*! 193 | Sets the database used by QDjango. 194 | 195 | You must call this method from your application's main thread. 196 | 197 | \sa database() 198 | */ 199 | void QDjango::setDatabase(QSqlDatabase database) 200 | { 201 | globalDatabaseType = getDatabaseType(database); 202 | if (globalDatabaseType == QDjangoDatabase::UnknownDB) { 203 | qWarning() << "Unsupported database driver" << database.driverName(); 204 | } 205 | 206 | if (!globalDatabase) { 207 | globalDatabase = new QDjangoDatabase(); 208 | qAddPostRoutine(closeDatabase); 209 | } 210 | initDatabase(database); 211 | globalDatabase->reference = database; 212 | } 213 | 214 | /*! 215 | Returns whether debugging information should be printed. 216 | 217 | \sa setDebugEnabled() 218 | */ 219 | bool QDjango::isDebugEnabled() 220 | { 221 | return globalDebugEnabled; 222 | } 223 | 224 | /*! 225 | Sets whether debugging information should be printed. 226 | 227 | \sa isDebugEnabled() 228 | */ 229 | void QDjango::setDebugEnabled(bool enabled) 230 | { 231 | globalDebugEnabled = enabled; 232 | } 233 | 234 | static void qdjango_topsort(const QByteArray &modelName, QHash &visited, 235 | QStack &stack) 236 | { 237 | visited[modelName] = true; 238 | QDjangoMetaModel model = globalMetaModels[modelName]; 239 | foreach (const QByteArray &foreignModel, model.foreignFields().values()) { 240 | if (!visited[foreignModel]) 241 | qdjango_topsort(foreignModel, visited, stack); 242 | } 243 | 244 | stack.push(model); 245 | } 246 | 247 | static QStack qdjango_sorted_metamodels() 248 | { 249 | QStack stack; 250 | stack.reserve(globalMetaModels.size()); 251 | QHash visited; 252 | visited.reserve(globalMetaModels.size()); 253 | foreach (const QByteArray &model, globalMetaModels.keys()) 254 | visited[model] = false; 255 | 256 | foreach (const QByteArray &model, globalMetaModels.keys()) { 257 | if (!visited[model]) 258 | qdjango_topsort(model, visited, stack); 259 | } 260 | 261 | return stack; 262 | } 263 | 264 | /*! 265 | Creates the database tables for all registered models. 266 | 267 | \return true if all the tables were created, false otherwise. 268 | */ 269 | bool QDjango::createTables() 270 | { 271 | bool result = true; 272 | QStack stack = qdjango_sorted_metamodels(); 273 | foreach (const QDjangoMetaModel &model, stack) { 274 | if (!model.createTable()) 275 | result = false; 276 | } 277 | 278 | return result; 279 | } 280 | 281 | /*! 282 | Drops the database tables for all registered models. 283 | 284 | \return true if all the tables were dropped, false otherwise. 285 | */ 286 | bool QDjango::dropTables() 287 | { 288 | bool result = true; 289 | QStack stack = qdjango_sorted_metamodels(); 290 | for (int i = stack.size() - 1; i >= 0; --i) { 291 | QDjangoMetaModel model = stack.at(i); 292 | if (!model.dropTable()) 293 | result = false; 294 | } 295 | 296 | return result; 297 | } 298 | 299 | /*! 300 | Returns the QDjangoMetaModel with the given \a name. 301 | */ 302 | QDjangoMetaModel QDjango::metaModel(const char *name) 303 | { 304 | if (globalMetaModels.contains(name)) 305 | return globalMetaModels.value(name); 306 | 307 | // otherwise, try to find a model anyway 308 | foreach (QByteArray modelName, globalMetaModels.keys()) { 309 | if (qstricmp(name, modelName.data()) == 0) 310 | return globalMetaModels.value(modelName); 311 | } 312 | 313 | return QDjangoMetaModel(); 314 | } 315 | 316 | QDjangoMetaModel QDjango::registerModel(const QMetaObject *meta) 317 | { 318 | const QByteArray name = meta->className(); 319 | if (!globalMetaModels.contains(name)) 320 | globalMetaModels.insert(name, QDjangoMetaModel(meta)); 321 | return globalMetaModels[name]; 322 | } 323 | 324 | QDjangoDatabase::DatabaseType QDjangoDatabase::databaseType(const QSqlDatabase &db) 325 | { 326 | Q_UNUSED(db); 327 | return globalDatabaseType; 328 | } 329 | -------------------------------------------------------------------------------- /src/db/QDjango.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_H 19 | #define QDJANGO_H 20 | 21 | #include "QDjangoMetaModel.h" 22 | 23 | class QObject; 24 | class QSqlDatabase; 25 | class QSqlQuery; 26 | class QString; 27 | 28 | /** \brief The QDjango class provides a set of static functions. 29 | * 30 | * It is used to access registered QDjangoModel classes. 31 | * 32 | * \ingroup Database 33 | */ 34 | class QDJANGO_DB_EXPORT QDjango 35 | { 36 | public: 37 | static bool createTables(); 38 | static bool dropTables(); 39 | 40 | static QSqlDatabase database(); 41 | static void setDatabase(QSqlDatabase database); 42 | 43 | static bool isDebugEnabled(); 44 | static void setDebugEnabled(bool enabled); 45 | 46 | template 47 | static QDjangoMetaModel registerModel(); 48 | 49 | private: 50 | static QDjangoMetaModel registerModel(const QMetaObject *meta); 51 | static QDjangoMetaModel metaModel(const char *name); 52 | 53 | friend class QDjangoCompiler; 54 | friend class QDjangoModel; 55 | friend class QDjangoMetaModel; 56 | friend class QDjangoQuerySetPrivate; 57 | }; 58 | 59 | /** Register a QDjangoModel class with QDjango. 60 | */ 61 | template 62 | QDjangoMetaModel QDjango::registerModel() 63 | { 64 | return registerModel(&T::staticMetaObject); 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/db/QDjangoMetaModel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGOMETAMODEL_H 19 | #define QDJANGOMETAMODEL_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "QDjango_p.h" 27 | 28 | class QDjangoMetaFieldPrivate; 29 | class QDjangoMetaModelPrivate; 30 | 31 | /** \brief The QDjangoMetaField class holds the database schema for a field. 32 | * 33 | * \internal 34 | */ 35 | class QDJANGO_DB_EXPORT QDjangoMetaField 36 | { 37 | public: 38 | QDjangoMetaField(); 39 | QDjangoMetaField(const QDjangoMetaField &other); 40 | ~QDjangoMetaField(); 41 | QDjangoMetaField& operator=(const QDjangoMetaField &other); 42 | 43 | QString column() const; 44 | bool isAutoIncrement() const; 45 | bool isBlank() const; 46 | bool isNullable() const; 47 | bool isUnique() const; 48 | bool isValid() const; 49 | QString name() const; 50 | int maxLength() const; 51 | QVariant toDatabase(const QVariant &value) const; 52 | 53 | private: 54 | QSharedDataPointer d; 55 | friend class QDjangoMetaModel; 56 | }; 57 | 58 | /** \brief The QDjangoMetaModel class holds the database schema for a model. 59 | * 60 | * It manages table creation and deletion operations as well as row 61 | * serialisation, deserialisation and deletion operations. 62 | * 63 | * \internal 64 | */ 65 | class QDJANGO_DB_EXPORT QDjangoMetaModel 66 | { 67 | public: 68 | QDjangoMetaModel(const QMetaObject *model = 0); 69 | QDjangoMetaModel(const QDjangoMetaModel &other); 70 | ~QDjangoMetaModel(); 71 | QDjangoMetaModel& operator=(const QDjangoMetaModel &other); 72 | 73 | bool isValid() const; 74 | 75 | bool createTable() const; 76 | QStringList createTableSql() const; 77 | bool dropTable() const; 78 | 79 | void load(QObject *model, const QVariantList &props, int &pos, const QStringList &relatedFields = QStringList()) const; 80 | bool remove(QObject *model) const; 81 | bool save(QObject *model) const; 82 | 83 | QObject *foreignKey(const QObject *model, const char *name) const; 84 | void setForeignKey(QObject *model, const char *name, QObject *value) const; 85 | 86 | QString className() const; 87 | QDjangoMetaField localField(const char *name) const; 88 | QList localFields() const; 89 | QMap foreignFields() const; 90 | QByteArray primaryKey() const; 91 | QString table() const; 92 | 93 | private: 94 | QSharedDataPointer d; 95 | }; 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /src/db/QDjangoModel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #include "QDjango.h" 22 | #include "QDjangoModel.h" 23 | #include "QDjangoQuerySet.h" 24 | 25 | /** Construct a new QDjangoModel. 26 | * 27 | * \param parent 28 | */ 29 | QDjangoModel::QDjangoModel(QObject *parent) 30 | : QObject(parent) 31 | { 32 | } 33 | 34 | /** Returns the primary key for this QDjangoModel. 35 | */ 36 | QVariant QDjangoModel::pk() const 37 | { 38 | const QDjangoMetaModel metaModel = QDjango::metaModel(metaObject()->className()); 39 | return property(metaModel.primaryKey()); 40 | } 41 | 42 | /** Sets the primary key for this QDjangoModel. 43 | * 44 | * \param pk 45 | */ 46 | void QDjangoModel::setPk(const QVariant &pk) 47 | { 48 | const QDjangoMetaModel metaModel = QDjango::metaModel(metaObject()->className()); 49 | setProperty(metaModel.primaryKey(), pk); 50 | } 51 | 52 | /** Retrieves the QDjangoModel pointed to by the given foreign-key. 53 | * 54 | * \param name 55 | */ 56 | QObject *QDjangoModel::foreignKey(const char *name) const 57 | { 58 | const QDjangoMetaModel metaModel = QDjango::metaModel(metaObject()->className()); 59 | return metaModel.foreignKey(this, name); 60 | } 61 | 62 | /** Sets the QDjangoModel pointed to by the given foreign-key. 63 | * 64 | * \param name 65 | * \param value 66 | * 67 | * \note The QDjangoModel will not take ownership of the given \c value. 68 | */ 69 | void QDjangoModel::setForeignKey(const char *name, QObject *value) 70 | { 71 | const QDjangoMetaModel metaModel = QDjango::metaModel(metaObject()->className()); 72 | metaModel.setForeignKey(this, name, value); 73 | } 74 | 75 | /** Deletes the QDjangoModel from the database. 76 | * 77 | * \return true if deletion succeeded, false otherwise 78 | */ 79 | bool QDjangoModel::remove() 80 | { 81 | const QDjangoMetaModel metaModel = QDjango::metaModel(metaObject()->className()); 82 | return metaModel.remove(this); 83 | } 84 | 85 | /** Saves the QDjangoModel to the database. 86 | * 87 | * \return true if saving succeeded, false otherwise 88 | */ 89 | bool QDjangoModel::save() 90 | { 91 | const QDjangoMetaModel metaModel = QDjango::metaModel(metaObject()->className()); 92 | return metaModel.save(this); 93 | } 94 | 95 | /** Returns a string representation of the model instance. 96 | */ 97 | QString QDjangoModel::toString() const 98 | { 99 | const QDjangoMetaModel metaModel = QDjango::metaModel(metaObject()->className()); 100 | const QByteArray pkName = metaModel.primaryKey(); 101 | return QString::fromLatin1("%1(%2=%3)").arg(QString::fromLatin1(metaObject()->className()), QString::fromLatin1(pkName), property(pkName).toString()); 102 | } 103 | 104 | -------------------------------------------------------------------------------- /src/db/QDjangoModel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_MODEL_H 19 | #define QDJANGO_MODEL_H 20 | 21 | #include 22 | #include 23 | 24 | #include "QDjango_p.h" 25 | 26 | /** \brief The QDjangoModel class is the base class for all models. 27 | * 28 | * To declare your own model, create a class which inherits QDjangoModel 29 | * and declare the database fields as properties using the Q_PROPERTY 30 | * macro. You must then register the class with QDjango using 31 | * QDjango::registerModel(). 32 | * 33 | * You can provide options for the model using the Q_CLASSINFO macro as 34 | * follows: 35 | * 36 | * \code 37 | * Q_CLASSINFO("__meta__", "keyword1=value1 .. keywordN=valueN") 38 | * \endcode 39 | * 40 | * The following keywords are recognised for model options: 41 | * 42 | * \li \c db_table if provided, this is the name of the database table for 43 | * the model, otherwise the lowercased class name will be used 44 | * \li \c unique_together set of fields that, taken together, must be unique. 45 | * If provided, a UNIQUE statement is included in the CREATE TABLE statement. 46 | * Example: \c unique_together=some_field,other_field 47 | * 48 | * You can also provide additional information about a field using the 49 | * Q_CLASSINFO macro, in the form: 50 | * 51 | * \code 52 | * Q_CLASSINFO("field_name", "keyword1=value1 .. keywordN=valueN") 53 | * \endcode 54 | * 55 | * The following keywords are recognised for field options: 56 | * 57 | * \li \c auto_increment if set to 'true', and if this field is the primary 58 | * key, it will be marked as auto-increment. 59 | * \li \c blank if set to 'true', this field is allowed to be empty. 60 | * \li \c db_column if provided, this is the name of the database column for 61 | * the field, otherwise the field name will be used 62 | * \li \c db_index if set to 'true', an index will be created on this field. 63 | * \li \c ignore_field if set to 'true', this field will be ignored 64 | * \li \c max_length the maximum length of the field (used when creating 65 | * the database table) 66 | * \li \c null if set to 'true', empty values will be stored as NULL. The 67 | * default value is 'false'. 68 | * \li \c primary_key if set to 'true', this field will be used as the primary 69 | * key. If no primary key is explicitly defined, an auto-increment integer 70 | * field will be added. 71 | * \li \c unique if set to 'true', this field must be unique throughout the 72 | * table. 73 | * \li \c on_delete if provided, create a foreign key constraint on this field. 74 | * Accepted values are: 'cascade', 'restrict', and 'set_null' 75 | * 76 | * \ingroup Database 77 | */ 78 | class QDJANGO_DB_EXPORT QDjangoModel : public QObject 79 | { 80 | Q_OBJECT 81 | Q_PROPERTY(QVariant pk READ pk WRITE setPk) 82 | Q_CLASSINFO("pk", "ignore_field=true") 83 | 84 | public: 85 | QDjangoModel(QObject *parent = 0); 86 | 87 | QVariant pk() const; 88 | void setPk(const QVariant &pk); 89 | 90 | public slots: 91 | bool remove(); 92 | bool save(); 93 | QString toString() const; 94 | 95 | protected: 96 | QObject *foreignKey(const char *name) const; 97 | void setForeignKey(const char *name, QObject *value); 98 | }; 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /src/db/QDjangoQuerySet_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_QUERYSET_P_H 19 | #define QDJANGO_QUERYSET_P_H 20 | 21 | // 22 | // W A R N I N G 23 | // ------------- 24 | // 25 | // This file is not part of the QDjango API. 26 | // 27 | 28 | #include 29 | 30 | #include "QDjango_p.h" 31 | #include "QDjangoWhere.h" 32 | 33 | class QDjangoMetaModel; 34 | 35 | class QDJANGO_DB_EXPORT QDjangoModelReference 36 | { 37 | public: 38 | QDjangoModelReference(const QString &tableReference_ = QString(), const QDjangoMetaModel &metaModel_ = QDjangoMetaModel(), bool nullable_ = false) 39 | : tableReference(tableReference_) 40 | , metaModel(metaModel_) 41 | , nullable(nullable_) 42 | { 43 | }; 44 | 45 | QString tableReference; 46 | QDjangoMetaModel metaModel; 47 | bool nullable; 48 | }; 49 | 50 | class QDJANGO_DB_EXPORT QDjangoReverseReference 51 | { 52 | public: 53 | QString leftHandKey; 54 | QString rightHandKey; 55 | }; 56 | 57 | /** \internal 58 | */ 59 | class QDJANGO_DB_EXPORT QDjangoCompiler 60 | { 61 | public: 62 | QDjangoCompiler(const char *modelName, const QSqlDatabase &db); 63 | QString fromSql(); 64 | QStringList fieldNames(bool recurse, const QStringList *fields = 0, QDjangoMetaModel *metaModel = 0, const QString &modelPath = QString(), bool nullable = false); 65 | QString orderLimitSql(const QStringList &orderBy, int lowMark, int highMark); 66 | void resolve(QDjangoWhere &where); 67 | 68 | private: 69 | QString databaseColumn(const QString &name); 70 | QString referenceModel(const QString &modelPath, QDjangoMetaModel *metaModel, bool nullable); 71 | 72 | QSqlDriver *driver; 73 | QDjangoMetaModel baseModel; 74 | QMap modelRefs; 75 | QMap reverseModelRefs; 76 | QMap fieldColumnCache; 77 | }; 78 | 79 | /** \internal 80 | */ 81 | class QDJANGO_DB_EXPORT QDjangoQuerySetPrivate 82 | { 83 | public: 84 | QDjangoQuerySetPrivate(const char *modelName); 85 | 86 | void addFilter(const QDjangoWhere &where); 87 | QDjangoWhere resolvedWhere(const QSqlDatabase &db) const; 88 | bool sqlDelete(); 89 | bool sqlFetch(); 90 | bool sqlInsert(const QVariantMap &fields, QVariant *insertId = 0); 91 | bool sqlLoad(QObject *model, int index); 92 | int sqlUpdate(const QVariantMap &fields); 93 | QList sqlValues(const QStringList &fields); 94 | QList sqlValuesList(const QStringList &fields); 95 | 96 | // SQL queries 97 | QDjangoQuery aggregateQuery(const QDjangoWhere::AggregateType func, const QString &field) const; 98 | QDjangoQuery deleteQuery() const; 99 | QDjangoQuery insertQuery(const QVariantMap &fields) const; 100 | QDjangoQuery selectQuery() const; 101 | QDjangoQuery updateQuery(const QVariantMap &fields) const; 102 | 103 | // reference counter 104 | QAtomicInt counter; 105 | 106 | bool hasResults; 107 | int lowMark; 108 | int highMark; 109 | QDjangoWhere whereClause; 110 | QStringList orderBy; 111 | QList properties; 112 | bool selectRelated; 113 | QStringList relatedFields; 114 | 115 | private: 116 | Q_DISABLE_COPY(QDjangoQuerySetPrivate) 117 | 118 | QByteArray m_modelName; 119 | 120 | friend class QDjangoMetaModel; 121 | }; 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /src/db/QDjangoWhere.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_WHERE_H 19 | #define QDJANGO_WHERE_H 20 | 21 | #include 22 | #include 23 | 24 | #include "QDjango_p.h" 25 | 26 | class QDjangoMetaModel; 27 | class QDjangoQuery; 28 | class QDjangoWherePrivate; 29 | 30 | /** \brief The QDjangoWhere class expresses an SQL constraint. 31 | * 32 | * The QDjangoWhere class is used to build SQL WHERE statements. In its 33 | * simplest form a QDjangoWhere expresses a constraint on a column value. 34 | * 35 | * QDjangoWhere instances can be negated using the "!" unary operator 36 | * or combined using the "&&" and "||" boolean operators. 37 | * 38 | * \ingroup Database 39 | */ 40 | class QDJANGO_DB_EXPORT QDjangoWhere 41 | { 42 | public: 43 | enum Operation 44 | { 45 | None, 46 | Equals, 47 | NotEquals, 48 | GreaterThan, 49 | LessThan, 50 | GreaterOrEquals, 51 | LessOrEquals, 52 | StartsWith, 53 | EndsWith, 54 | Contains, 55 | IsIn, 56 | IsNull, 57 | IEquals, 58 | INotEquals, 59 | IStartsWith, 60 | IEndsWith, 61 | IContains 62 | }; 63 | 64 | enum AggregateType{ 65 | AVG, 66 | COUNT, 67 | SUM, 68 | MIN, 69 | MAX 70 | }; 71 | QDjangoWhere(); 72 | QDjangoWhere(const QDjangoWhere &other); 73 | QDjangoWhere(const QString &key, QDjangoWhere::Operation operation, QVariant value); 74 | ~QDjangoWhere(); 75 | 76 | QDjangoWhere& operator=(const QDjangoWhere &other); 77 | QDjangoWhere operator!() const; 78 | QDjangoWhere operator&&(const QDjangoWhere &other) const; 79 | QDjangoWhere operator||(const QDjangoWhere &other) const; 80 | 81 | void bindValues(QDjangoQuery &query) const; 82 | bool isAll() const; 83 | bool isNone() const; 84 | QString sql(const QSqlDatabase &db) const; 85 | QString toString() const; 86 | 87 | private: 88 | QSharedDataPointer d; 89 | friend class QDjangoCompiler; 90 | }; 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /src/db/QDjangoWhere_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_WHERE_P_H 19 | #define QDJANGO_WHERE_P_H 20 | 21 | // 22 | // W A R N I N G 23 | // ------------- 24 | // 25 | // This file is not part of the QDjango API. 26 | // 27 | 28 | #include 29 | 30 | #include "QDjangoWhere.h" 31 | 32 | class QDjangoWherePrivate : public QSharedData 33 | { 34 | public: 35 | static QString operationToString(QDjangoWhere::Operation operation); 36 | 37 | enum Combine 38 | { 39 | NoCombine, 40 | AndCombine, 41 | OrCombine 42 | }; 43 | static QString combineToString(Combine combine); 44 | 45 | QDjangoWherePrivate(); 46 | 47 | QString key; 48 | QDjangoWhere::Operation operation; 49 | QVariant data; 50 | 51 | QList children; 52 | Combine combine; 53 | bool negate; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/db/QDjango_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_P_H 19 | #define QDJANGO_P_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #if defined(QDJANGO_SHARED) 30 | # if defined(QDJANGO_DB_BUILD) 31 | # define QDJANGO_DB_EXPORT Q_DECL_EXPORT 32 | # else 33 | # define QDJANGO_DB_EXPORT Q_DECL_IMPORT 34 | # endif 35 | #else 36 | # define QDJANGO_DB_EXPORT 37 | #endif 38 | 39 | /** \brief The QDjangoDatabase class represents a set of connections to a 40 | * database. 41 | * 42 | * \internal 43 | */ 44 | class QDJANGO_DB_EXPORT QDjangoDatabase : public QObject 45 | { 46 | Q_OBJECT 47 | 48 | public: 49 | QDjangoDatabase(QObject *parent = 0); 50 | 51 | enum DatabaseType { 52 | UnknownDB, 53 | MSSqlServer, 54 | MySqlServer, 55 | PostgreSQL, 56 | Oracle, 57 | Sybase, 58 | SQLite, 59 | Interbase, 60 | DB2 61 | }; 62 | 63 | static DatabaseType databaseType(const QSqlDatabase &db); 64 | 65 | QSqlDatabase reference; 66 | QMutex mutex; 67 | QMap copies; 68 | qint64 connectionId; 69 | 70 | private slots: 71 | void threadFinished(); 72 | }; 73 | 74 | class QDJANGO_DB_EXPORT QDjangoQuery : public QSqlQuery 75 | { 76 | public: 77 | QDjangoQuery(QSqlDatabase db); 78 | void addBindValue(const QVariant &val, QSql::ParamType paramType = QSql::In); 79 | bool exec(); 80 | bool exec(const QString &query); 81 | }; 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /src/db/db.pro: -------------------------------------------------------------------------------- 1 | include(../../qdjango.pri) 2 | 3 | QT -= gui 4 | QT += sql 5 | 6 | DEFINES += QDJANGO_DB_BUILD 7 | 8 | TARGET = qdjango-db 9 | win32 { 10 | DESTDIR = $$OUT_PWD 11 | } 12 | 13 | HEADERS += \ 14 | QDjango.h \ 15 | QDjango_p.h \ 16 | QDjangoMetaModel.h \ 17 | QDjangoModel.h \ 18 | QDjangoQuerySet.h \ 19 | QDjangoQuerySet_p.h \ 20 | QDjangoWhere.h \ 21 | QDjangoWhere_p.h 22 | SOURCES += \ 23 | QDjango.cpp \ 24 | QDjangoMetaModel.cpp \ 25 | QDjangoModel.cpp \ 26 | QDjangoQuerySet.cpp \ 27 | QDjangoWhere.cpp 28 | 29 | # Installation 30 | include(../src.pri) 31 | headers.path = $$PREFIX/include/qdjango/db 32 | QMAKE_PKGCONFIG_INCDIR = $$headers.path 33 | -------------------------------------------------------------------------------- /src/http/QDjangoFastCgiServer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_FASTCGI_SERVER_H 19 | #define QDJANGO_FASTCGI_SERVER_H 20 | 21 | #include 22 | #include 23 | 24 | #include "QDjangoHttp_p.h" 25 | 26 | class QDjangoFastCgiServerPrivate; 27 | class QDjangoHttpController; 28 | class QDjangoUrlResolver; 29 | 30 | /** \brief The QDjangoFastCgiServer class represents a FastCGI server. 31 | * 32 | * It allows you to create a FastCGI server which your reverse proxy 33 | * (e.g. apache, nginx) will query to serve your web application. 34 | * 35 | * To register views, see urls(). 36 | * 37 | * \ingroup Http 38 | * \sa QDjangoHttpServer 39 | */ 40 | class QDJANGO_HTTP_EXPORT QDjangoFastCgiServer : public QObject 41 | { 42 | Q_OBJECT 43 | 44 | public: 45 | QDjangoFastCgiServer(QObject *parent = 0); 46 | ~QDjangoFastCgiServer(); 47 | 48 | void close(); 49 | bool listen(const QString &name); 50 | bool listen(const QHostAddress &address, quint16 port); 51 | QDjangoUrlResolver *urls() const; 52 | 53 | private slots: 54 | void _q_newLocalConnection(); 55 | void _q_newTcpConnection(); 56 | 57 | private: 58 | Q_DISABLE_COPY(QDjangoFastCgiServer) 59 | QDjangoFastCgiServerPrivate *d; 60 | }; 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/http/QDjangoFastCgiServer_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_FASTCGI_SERVER_P_H 19 | #define QDJANGO_FASTCGI_SERVER_P_H 20 | 21 | // 22 | // W A R N I N G 23 | // ------------- 24 | // 25 | // This file is not part of the QDjango API. 26 | // 27 | 28 | #include "QDjangoHttp_p.h" 29 | #include 30 | 31 | #define FCGI_HEADER_LEN 8 32 | #define FCGI_RECORD_SIZE (255*255 + 255 + 8) 33 | 34 | #define FCGI_BEGIN_REQUEST 1 35 | #define FCGI_ABORT_REQUEST 2 36 | #define FCGI_END_REQUEST 3 37 | #define FCGI_PARAMS 4 38 | #define FCGI_STDIN 5 39 | #define FCGI_STDOUT 6 40 | 41 | #define FCGI_KEEP_CONN 1 42 | 43 | class QDjangoFastCgiServer; 44 | class QDjangoHttpRequest; 45 | class QDjangoHttpResponse; 46 | class QIODevice; 47 | 48 | typedef struct { 49 | unsigned char version; 50 | unsigned char type; 51 | unsigned char requestIdB1; 52 | unsigned char requestIdB0; 53 | unsigned char contentLengthB1; 54 | unsigned char contentLengthB0; 55 | unsigned char paddingLength; 56 | unsigned char reserved; 57 | } FCGI_Header; 58 | 59 | class QDJANGO_HTTP_AUTOTEST_EXPORT QDjangoFastCgiHeader 60 | { 61 | public: 62 | static quint16 contentLength(FCGI_Header *header); 63 | static quint16 requestId(FCGI_Header *header); 64 | static void setContentLength(FCGI_Header *header, quint16 contentLength); 65 | static void setRequestId(FCGI_Header *header, quint16 requestId); 66 | }; 67 | 68 | class QDJANGO_HTTP_AUTOTEST_EXPORT QDjangoFastCgiConnection : public QObject 69 | { 70 | Q_OBJECT 71 | 72 | public: 73 | QDjangoFastCgiConnection(QIODevice *device, QDjangoFastCgiServer *server); 74 | ~QDjangoFastCgiConnection(); 75 | 76 | signals: 77 | void closed(); 78 | 79 | private slots: 80 | void _q_bytesWritten(qint64 bytes); 81 | void _q_readyRead(); 82 | 83 | private: 84 | void writeResponse(quint16 requestId, QDjangoHttpResponse *response); 85 | 86 | QIODevice *m_device; 87 | char m_inputBuffer[FCGI_RECORD_SIZE]; 88 | int m_inputPos; 89 | bool m_keepConnection; 90 | char m_outputBuffer[FCGI_RECORD_SIZE]; 91 | QDjangoHttpRequest *m_pendingRequest; 92 | quint16 m_pendingRequestId; 93 | QDjangoFastCgiServer *m_server; 94 | }; 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpController.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "QDjangoHttpController.h" 28 | #include "QDjangoHttpRequest.h" 29 | #include "QDjangoHttpResponse.h" 30 | 31 | /** Extract basic credentials from an HTTP \a request. 32 | * 33 | * Returns \b true if credentials were provider, \b false otherwise. 34 | */ 35 | bool QDjangoHttpController::getBasicAuth(const QDjangoHttpRequest &request, QString &username, QString &password) 36 | { 37 | QRegExp authRx(QLatin1String("^Basic (.+)$")); 38 | const QString authHeader = request.meta(QLatin1String("HTTP_AUTHORIZATION")); 39 | if (authRx.exactMatch(authHeader)) { 40 | const QString authValue = QString::fromUtf8(QByteArray::fromBase64(authRx.cap(1).toLatin1())); 41 | const QStringList bits = authValue.split(QLatin1Char(':')); 42 | if (bits.size() == 2 && !bits[0].isEmpty() && !bits[1].isEmpty()) { 43 | username = bits[0]; 44 | password = bits[1]; 45 | return true; 46 | } 47 | } 48 | return false; 49 | } 50 | 51 | /** Converts a QDateTime to an HTTP datetime string. 52 | */ 53 | QString QDjangoHttpController::httpDateTime(const QDateTime &dt) 54 | { 55 | if (dt.isValid()) 56 | return QLocale("C").toString(dt.toUTC(), QLatin1String("ddd, dd MMM yyyy HH:mm:ss")) + QLatin1String(" GMT"); 57 | return QString(); 58 | } 59 | 60 | /** Converts an HTTP datetime string to a QDateTime. 61 | */ 62 | QDateTime QDjangoHttpController::httpDateTime(const QString &str) 63 | { 64 | QDateTime dt = QLocale("C").toDateTime(str.left(25), QLatin1String("ddd, dd MMM yyyy HH:mm:ss")); 65 | dt.setTimeSpec(Qt::UTC); 66 | return dt; 67 | } 68 | 69 | QDjangoHttpResponse *QDjangoHttpController::serveError(const QDjangoHttpRequest &request, int code, const QString &text) 70 | { 71 | Q_UNUSED(request); 72 | 73 | QDjangoHttpResponse *response = new QDjangoHttpResponse; 74 | response->setHeader(QLatin1String("Content-Type"), QLatin1String("text/html; charset=utf-8")); 75 | response->setStatusCode(code); 76 | response->setBody(QString::fromLatin1("" 77 | "Error" 78 | "

%1

" 79 | "").arg(text).toUtf8()); 80 | return response; 81 | } 82 | 83 | /** Respond to an HTTP \a request with an authorization error. 84 | * 85 | * \param request 86 | * \param realm 87 | */ 88 | QDjangoHttpResponse *QDjangoHttpController::serveAuthorizationRequired(const QDjangoHttpRequest &request, const QString &realm) 89 | { 90 | Q_UNUSED(request); 91 | 92 | QDjangoHttpResponse *response = new QDjangoHttpResponse; 93 | response->setStatusCode(QDjangoHttpResponse::AuthorizationRequired); 94 | response->setHeader(QLatin1String("WWW-Authenticate"), QString::fromLatin1("Basic realm=\"%1\"").arg(realm)); 95 | return response; 96 | } 97 | 98 | /** Respond to a malformed HTTP request. 99 | * 100 | * \param request 101 | */ 102 | QDjangoHttpResponse *QDjangoHttpController::serveBadRequest(const QDjangoHttpRequest &request) 103 | { 104 | return serveError(request, QDjangoHttpResponse::BadRequest, QLatin1String("Your browser sent a malformed request.")); 105 | } 106 | 107 | /** Respond to an HTTP \a request with an internal server error. 108 | * 109 | * \param request 110 | */ 111 | QDjangoHttpResponse *QDjangoHttpController::serveInternalServerError(const QDjangoHttpRequest &request) 112 | { 113 | return serveError(request, QDjangoHttpResponse::InternalServerError, QLatin1String("An internal server error was encountered.")); 114 | } 115 | 116 | /** Respond to an HTTP \a request with a not found error. 117 | * 118 | * \param request 119 | */ 120 | QDjangoHttpResponse *QDjangoHttpController::serveNotFound(const QDjangoHttpRequest &request) 121 | { 122 | return serveError(request, QDjangoHttpResponse::NotFound, QLatin1String("The document you requested was not found.")); 123 | } 124 | 125 | /** Respond to an HTTP \a request with a redirect. 126 | * 127 | * \param request 128 | * \param url The URL to which the user is redirected. 129 | * \param permanent Whether the redirect is permanent. 130 | */ 131 | QDjangoHttpResponse *QDjangoHttpController::serveRedirect(const QDjangoHttpRequest &request, const QUrl &url, bool permanent) 132 | { 133 | const QString urlString = url.toString(); 134 | QDjangoHttpResponse *response = serveError(request, permanent ? QDjangoHttpResponse::MovedPermanently : QDjangoHttpResponse::Found, 135 | QString::fromLatin1("You are being redirect to %2").arg(urlString, urlString)); 136 | response->setHeader(QLatin1String("Location"), urlString); 137 | return response; 138 | } 139 | 140 | /** Respond to an HTTP \a request for a static file. 141 | * 142 | * \param request 143 | * \param docPath The path to the document, such that it can be opened using a QFile. 144 | * \param expires An optional expiry date. 145 | */ 146 | QDjangoHttpResponse *QDjangoHttpController::serveStatic(const QDjangoHttpRequest &request, const QString &docPath, const QDateTime &expires) 147 | { 148 | QFileInfo info(docPath); 149 | if (!info.isFile()) 150 | return serveNotFound(request); 151 | const QString fileName = info.fileName(); 152 | 153 | QDjangoHttpResponse *response = new QDjangoHttpResponse; 154 | response->setStatusCode(QDjangoHttpResponse::OK); 155 | 156 | // determine last modified date 157 | QDateTime lastModified = info.lastModified(); 158 | if (docPath.startsWith(QLatin1String(":/"))) 159 | lastModified = QFileInfo(qApp->applicationFilePath()).lastModified(); 160 | if (lastModified.isValid()) 161 | response->setHeader(QLatin1String("Last-Modified"), httpDateTime(lastModified)); 162 | 163 | // cache expiry 164 | if (expires.isValid()) 165 | response->setHeader(QLatin1String("Expires"), httpDateTime(expires)); 166 | 167 | // handle if-modified-since 168 | const QDateTime ifModifiedSince = httpDateTime(request.meta(QLatin1String("HTTP_IF_MODIFIED_SINCE"))); 169 | if (lastModified.isValid() && ifModifiedSince.isValid() && lastModified <= ifModifiedSince) 170 | { 171 | response->setStatusCode(304); 172 | return response; 173 | } 174 | 175 | // determine content type 176 | QString mimeType; 177 | if (fileName.endsWith(QLatin1String(".css"))) 178 | mimeType = QLatin1String("text/css"); 179 | else if (fileName.endsWith(QLatin1String(".html"))) 180 | mimeType = QLatin1String("text/html"); 181 | else if (fileName.endsWith(QLatin1String(".js"))) 182 | mimeType = QLatin1String("application/javascript"); 183 | else 184 | mimeType = QLatin1String("application/octet-stream"); 185 | response->setHeader(QLatin1String("Content-Type"), mimeType); 186 | 187 | // read contents 188 | QFile file(docPath); 189 | if (!file.open(QIODevice::ReadOnly)) { 190 | delete response; 191 | return serveInternalServerError(request); 192 | } 193 | if (request.method() == QLatin1String("HEAD")) 194 | response->setHeader(QLatin1String("Content-Length"), QString::number(file.size())); 195 | else 196 | response->setBody(file.readAll()); 197 | return response; 198 | } 199 | 200 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_HTTP_CONTROLLER_H 19 | #define QDJANGO_HTTP_CONTROLLER_H 20 | 21 | #include 22 | #include 23 | 24 | #include "QDjangoHttp_p.h" 25 | 26 | class QDjangoHttpRequest; 27 | class QDjangoHttpResponse; 28 | class QUrl; 29 | 30 | /** \brief The QDjangoHttpController class provides static methods for replying to HTTP requests. 31 | * 32 | * \ingroup Http 33 | */ 34 | class QDJANGO_HTTP_EXPORT QDjangoHttpController 35 | { 36 | public: 37 | // get basic authorization credentials 38 | static bool getBasicAuth(const QDjangoHttpRequest &request, QString &username, QString &password); 39 | 40 | // date / time handling 41 | static QString httpDateTime(const QDateTime &dt); 42 | static QDateTime httpDateTime(const QString &str); 43 | 44 | // common responses 45 | static QDjangoHttpResponse *serveAuthorizationRequired(const QDjangoHttpRequest &request, const QString &realm = QLatin1String("Secure Area")); 46 | static QDjangoHttpResponse *serveBadRequest(const QDjangoHttpRequest &request); 47 | static QDjangoHttpResponse *serveInternalServerError(const QDjangoHttpRequest &request); 48 | static QDjangoHttpResponse *serveNotFound(const QDjangoHttpRequest &request); 49 | static QDjangoHttpResponse *serveRedirect(const QDjangoHttpRequest &request, const QUrl &url, bool permanent = false); 50 | static QDjangoHttpResponse *serveStatic(const QDjangoHttpRequest &request, const QString &filePath, const QDateTime &expires = QDateTime()); 51 | 52 | private: 53 | static QDjangoHttpResponse *serveError(const QDjangoHttpRequest &request, int code, const QString &text); 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpRequest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) 20 | #include 21 | #else 22 | #include 23 | #endif 24 | 25 | #include "QDjangoHttpRequest.h" 26 | #include "QDjangoHttpRequest_p.h" 27 | 28 | /** Constructs a new HTTP request. 29 | */ 30 | QDjangoHttpRequest::QDjangoHttpRequest() 31 | : d(new QDjangoHttpRequestPrivate) 32 | { 33 | } 34 | 35 | /** Destroys the HTTP request. 36 | */ 37 | QDjangoHttpRequest::~QDjangoHttpRequest() 38 | { 39 | delete d; 40 | } 41 | 42 | /** Returns the raw body of the HTTP request. 43 | */ 44 | QByteArray QDjangoHttpRequest::body() const 45 | { 46 | return d->buffer; 47 | } 48 | 49 | /** Returns the GET data for the given \a key. 50 | */ 51 | QString QDjangoHttpRequest::get(const QString &key) const 52 | { 53 | QString queryString = d->meta.value(QLatin1String("QUERY_STRING")); 54 | queryString.replace('+', ' '); 55 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) 56 | QUrlQuery query(queryString); 57 | return query.queryItemValue(key, QUrl::FullyDecoded); 58 | #else 59 | QUrl url; 60 | url.setEncodedQuery(queryString.toLatin1()); 61 | return url.queryItemValue(key); 62 | #endif 63 | } 64 | 65 | /** Returns the specified HTTP request header. 66 | * 67 | * \param key 68 | */ 69 | QString QDjangoHttpRequest::meta(const QString &key) const 70 | { 71 | return d->meta.value(key); 72 | } 73 | 74 | /** Returns the HTTP request's method (e.g. GET, POST). 75 | */ 76 | QString QDjangoHttpRequest::method() const 77 | { 78 | return d->method; 79 | } 80 | 81 | /** Returns the HTTP request's path. 82 | */ 83 | QString QDjangoHttpRequest::path() const 84 | { 85 | return d->path; 86 | } 87 | 88 | /** Returns the POST data for the given \a key. 89 | */ 90 | QString QDjangoHttpRequest::post(const QString &key) const 91 | { 92 | QByteArray buffer = d->buffer; 93 | buffer.replace('+', ' '); 94 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) 95 | QUrlQuery query(QString::fromUtf8(buffer)); 96 | return query.queryItemValue(key, QUrl::FullyDecoded); 97 | #else 98 | QUrl url; 99 | url.setEncodedQuery(buffer); 100 | return url.queryItemValue(key); 101 | #endif 102 | } 103 | 104 | QDjangoHttpTestRequest::QDjangoHttpTestRequest(const QString &method, const QString &path) 105 | { 106 | d->method = method; 107 | d->path = path; 108 | } 109 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_HTTP_REQUEST_H 19 | #define QDJANGO_HTTP_REQUEST_H 20 | 21 | #include 22 | 23 | #include "QDjangoHttp_p.h" 24 | 25 | class QDjangoHttpRequestPrivate; 26 | 27 | /** \brief The QDjangoHttpRequest class represents an HTTP request. 28 | * 29 | * \ingroup Http 30 | */ 31 | class QDJANGO_HTTP_EXPORT QDjangoHttpRequest 32 | { 33 | public: 34 | QDjangoHttpRequest(); 35 | ~QDjangoHttpRequest(); 36 | 37 | QByteArray body() const; 38 | QString get(const QString &key) const; 39 | QString meta(const QString &key) const; 40 | QString method() const; 41 | QString path() const; 42 | QString post(const QString &key) const; 43 | 44 | private: 45 | Q_DISABLE_COPY(QDjangoHttpRequest) 46 | QDjangoHttpRequestPrivate* const d; 47 | friend class QDjangoFastCgiConnection; 48 | friend class QDjangoHttpConnection; 49 | friend class QDjangoHttpTestRequest; 50 | friend class tst_QDjangoHttpController; 51 | friend class tst_QDjangoHttpRequest; 52 | }; 53 | 54 | /** \cond */ 55 | 56 | class QDJANGO_HTTP_EXPORT QDjangoHttpTestRequest : public QDjangoHttpRequest 57 | { 58 | public: 59 | QDjangoHttpTestRequest(const QString &method, const QString &path); 60 | 61 | private: 62 | Q_DISABLE_COPY(QDjangoHttpTestRequest) 63 | }; 64 | 65 | /** \endcond */ 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpRequest_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_HTTP_REQUEST_P_H 19 | #define QDJANGO_HTTP_REQUEST_P_H 20 | 21 | // 22 | // W A R N I N G 23 | // ------------- 24 | // 25 | // This file is not part of the QDjango API. 26 | // 27 | 28 | #include 29 | 30 | /** \internal 31 | */ 32 | class QDjangoHttpRequestPrivate 33 | { 34 | public: 35 | QByteArray buffer; 36 | QMap meta; 37 | QString method; 38 | QString path; 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpResponse.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include "QDjangoHttpResponse.h" 19 | #include "QDjangoHttpResponse_p.h" 20 | 21 | /** Constructs a new HTTP response. 22 | */ 23 | QDjangoHttpResponse::QDjangoHttpResponse() 24 | : d(new QDjangoHttpResponsePrivate) 25 | { 26 | setHeader(QLatin1String("Content-Length"), QLatin1String("0")); 27 | setStatusCode(QDjangoHttpResponse::OK); 28 | } 29 | 30 | /** Destroys the HTTP response. 31 | */ 32 | QDjangoHttpResponse::~QDjangoHttpResponse() 33 | { 34 | delete d; 35 | } 36 | 37 | /** Returns the raw body of the HTTP response. 38 | */ 39 | QByteArray QDjangoHttpResponse::body() const 40 | { 41 | return d->body; 42 | } 43 | 44 | /** Sets the raw body of the HTTP response. 45 | * 46 | * The Content-Length header will be updated to reflect the body size. 47 | * 48 | * \param body 49 | */ 50 | void QDjangoHttpResponse::setBody(const QByteArray &body) 51 | { 52 | d->body = body; 53 | setHeader(QLatin1String("Content-Length"), QString::number(d->body.size())); 54 | } 55 | 56 | /** Returns the specified HTTP response header. 57 | * 58 | * \param key 59 | */ 60 | QString QDjangoHttpResponse::header(const QString &key) const 61 | { 62 | QString lowercaseKey = key.toLower(); 63 | QList >::ConstIterator it = d->headers.constBegin(); 64 | while (it != d->headers.constEnd()) { 65 | if ((*it).first.toLower() == lowercaseKey) 66 | return (*it).second; 67 | ++it; 68 | } 69 | return QString(); 70 | } 71 | 72 | /** Sets the specified HTTP response header. 73 | * 74 | * \param key 75 | * \param value 76 | */ 77 | void QDjangoHttpResponse::setHeader(const QString &key, const QString &value) 78 | { 79 | QString lowercaseKey = key.toLower(); 80 | QList >::Iterator it = d->headers.begin(); 81 | while (it != d->headers.end()) { 82 | if ((*it).first.toLower() == lowercaseKey) { 83 | (*it).second = value; 84 | return; 85 | } 86 | ++it; 87 | } 88 | // not found so add 89 | d->headers.append(qMakePair(key, value)); 90 | } 91 | 92 | /** Returns true if the response is ready to be sent. 93 | * 94 | * The default implementation always returns true. If you subclass 95 | * QDjangoHttpResponse to support responses which should only be sent 96 | * to the client at a later point, you need to reimplement this method 97 | * and emit the ready() signal once the response is ready. 98 | */ 99 | bool QDjangoHttpResponse::isReady() const 100 | { 101 | return true; 102 | } 103 | 104 | /** Returns the reason for the HTTP response status line. 105 | */ 106 | QString QDjangoHttpResponse::reasonPhrase() const 107 | { 108 | return d->reasonPhrase; 109 | } 110 | 111 | /** Returns the code for the HTTP response status line. 112 | */ 113 | int QDjangoHttpResponse::statusCode() const 114 | { 115 | return d->statusCode; 116 | } 117 | 118 | /** Sets the code for the HTTP response status line. 119 | * 120 | * \param code 121 | */ 122 | void QDjangoHttpResponse::setStatusCode(int code) 123 | { 124 | d->statusCode = code; 125 | switch(code) 126 | { 127 | case OK: 128 | d->reasonPhrase = QLatin1String("OK"); 129 | break; 130 | case MovedPermanently: 131 | d->reasonPhrase = QLatin1String("Moved Permanently"); 132 | break; 133 | case Found: 134 | d->reasonPhrase = QLatin1String("Found"); 135 | break; 136 | case NotModified: 137 | d->reasonPhrase = QLatin1String("Not Modified"); 138 | break; 139 | case BadRequest: 140 | d->reasonPhrase = QLatin1String("Bad Request"); 141 | break; 142 | case AuthorizationRequired: 143 | d->reasonPhrase = QLatin1String("Authorization Required"); 144 | break; 145 | case Forbidden: 146 | d->reasonPhrase = QLatin1String("Forbidden"); 147 | break; 148 | case NotFound: 149 | d->reasonPhrase = QLatin1String("Not Found"); 150 | break; 151 | case MethodNotAllowed: 152 | d->reasonPhrase = QLatin1String("Method Not Allowed"); 153 | break; 154 | case InternalServerError: 155 | d->reasonPhrase = QLatin1String("Internal Server Error"); 156 | break; 157 | default: 158 | d->reasonPhrase = QLatin1String(""); 159 | break; 160 | } 161 | } 162 | 163 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_HTTP_RESPONSE_H 19 | #define QDJANGO_HTTP_RESPONSE_H 20 | 21 | #include 22 | 23 | #include "QDjangoHttp_p.h" 24 | 25 | class QDjangoHttpResponsePrivate; 26 | 27 | /** \brief The QDjangoHttpResponse class represents an HTTP response. 28 | * 29 | * \ingroup Http 30 | */ 31 | class QDJANGO_HTTP_EXPORT QDjangoHttpResponse : public QObject 32 | { 33 | Q_OBJECT 34 | 35 | public: 36 | /** \brief Enum representing well-known HTTP status codes. 37 | */ 38 | enum HttpStatus { 39 | OK = 200, 40 | MovedPermanently = 301, 41 | Found = 302, 42 | NotModified = 304, 43 | BadRequest = 400, 44 | AuthorizationRequired = 401, 45 | Forbidden = 403, 46 | NotFound = 404, 47 | MethodNotAllowed = 405, 48 | InternalServerError = 500, 49 | }; 50 | 51 | QDjangoHttpResponse(); 52 | ~QDjangoHttpResponse(); 53 | 54 | QByteArray body() const; 55 | void setBody(const QByteArray &body); 56 | 57 | QString header(const QString &key) const; 58 | void setHeader(const QString &key, const QString &value); 59 | 60 | virtual bool isReady() const; 61 | 62 | QString reasonPhrase() const; 63 | int statusCode() const; 64 | void setStatusCode(int code); 65 | 66 | signals: 67 | /** Emit this signal from your QDjangoHttpResponse subclasses once 68 | * the response is ready to be sent to the client. 69 | * 70 | * \sa isReady() 71 | */ 72 | void ready(); 73 | 74 | private: 75 | Q_DISABLE_COPY(QDjangoHttpResponse) 76 | QDjangoHttpResponsePrivate* const d; 77 | friend class QDjangoFastCgiConnection; 78 | friend class QDjangoHttpConnection; 79 | }; 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpResponse_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_HTTP_RESPONSE_P_H 19 | #define QDJANGO_HTTP_RESPONSE_P_H 20 | 21 | // 22 | // W A R N I N G 23 | // ------------- 24 | // 25 | // This file is not part of the QDjango API. 26 | // 27 | 28 | #include 29 | #include 30 | 31 | /** \internal 32 | */ 33 | class QDjangoHttpResponsePrivate 34 | { 35 | public: 36 | int statusCode; 37 | QString reasonPhrase; 38 | QList > headers; 39 | QByteArray body; 40 | }; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpServer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "QDjangoHttpController.h" 26 | #include "QDjangoHttpRequest.h" 27 | #include "QDjangoHttpRequest_p.h" 28 | #include "QDjangoHttpResponse.h" 29 | #include "QDjangoHttpResponse_p.h" 30 | #include "QDjangoHttpServer.h" 31 | #include "QDjangoHttpServer_p.h" 32 | #include "QDjangoUrlResolver.h" 33 | 34 | //#define QDJANGO_DEBUG_HTTP 35 | 36 | // maximum request body size is 10 MB 37 | #define MAX_BODY_SIZE (10 * 1024 * 1024) 38 | 39 | /// \cond 40 | 41 | /** Constructs a new HTTP connection. 42 | */ 43 | QDjangoHttpConnection::QDjangoHttpConnection(QTcpSocket *device, QDjangoHttpServer *server) 44 | : QObject(server), 45 | m_closeAfterResponse(false), 46 | m_pendingRequest(0), 47 | m_requestCount(0), 48 | m_server(server), 49 | m_socket(device) 50 | { 51 | bool check; 52 | Q_UNUSED(check); 53 | 54 | m_socket->setParent(this); 55 | check = connect(m_socket, SIGNAL(bytesWritten(qint64)), 56 | this, SLOT(_q_bytesWritten(qint64))); 57 | Q_ASSERT(check); 58 | 59 | check = connect(m_socket, SIGNAL(disconnected()), 60 | this, SIGNAL(closed())); 61 | Q_ASSERT(check); 62 | 63 | check = connect(m_socket, SIGNAL(readyRead()), 64 | this, SLOT(_q_readyRead())); 65 | Q_ASSERT(check); 66 | } 67 | 68 | /** Destroys the HTTP connection. 69 | */ 70 | QDjangoHttpConnection::~QDjangoHttpConnection() 71 | { 72 | if (m_pendingRequest) 73 | delete m_pendingRequest; 74 | foreach (const QDjangoHttpJob &job, m_pendingJobs) { 75 | delete job.first; 76 | delete job.second; 77 | } 78 | } 79 | 80 | /** When bytes have been written, check whether we need to close 81 | * the connection. 82 | * 83 | * @param bytes 84 | */ 85 | void QDjangoHttpConnection::_q_bytesWritten(qint64 bytes) 86 | { 87 | Q_UNUSED(bytes); 88 | if (!m_socket->bytesToWrite()) { 89 | if (!m_pendingJobs.isEmpty()) { 90 | _q_writeResponse(); 91 | } else if (m_closeAfterResponse) { 92 | #ifdef QDJANGO_DEBUG_HTTP 93 | qDebug("Closing connection"); 94 | #endif 95 | m_socket->close(); 96 | emit closed(); 97 | } 98 | } 99 | } 100 | 101 | /** Handle incoming data on the socket. 102 | */ 103 | void QDjangoHttpConnection::_q_readyRead() 104 | { 105 | QDjangoHttpRequest *request = m_pendingRequest; 106 | if (!request) { 107 | request = new QDjangoHttpRequest; 108 | m_requestBytesRemaining = 0; 109 | m_requestHeaderLine = 0; 110 | m_requestHeaderReceived = false; 111 | m_requestHeaders.clear(); 112 | m_requestMajorVersion = 0; 113 | m_requestMinorVersion = 0; 114 | m_requestPath.clear(); 115 | } 116 | 117 | // Read request header 118 | while (!m_requestHeaderReceived && m_socket->canReadLine()) { 119 | const QString line = QString::fromUtf8(m_socket->readLine()); 120 | 121 | if (!m_requestHeaderLine++) { 122 | bool ok = false; 123 | QStringList lst = line.simplified().split(QLatin1String(" ")); 124 | if (lst.count() > 0) { 125 | request->d->method = lst[0]; 126 | if (lst.count() > 1) { 127 | m_requestPath = lst[1]; 128 | request->d->path = QUrl(m_requestPath).path(); 129 | if (lst.count() > 2) { 130 | QString v = lst[2]; 131 | if (v.length() >= 8 && v.left(5) == QLatin1String("HTTP/") && 132 | v[5].isDigit() && v[6] == QLatin1Char('.') && v[7].isDigit()) { 133 | m_requestMajorVersion = v[5].toLatin1() - '0'; 134 | m_requestMinorVersion = v[7].toLatin1() - '0'; 135 | ok = true; 136 | } 137 | } 138 | } 139 | } 140 | if (!ok) { 141 | qWarning("Invalid HTTP request"); 142 | m_socket->close(); 143 | return; 144 | } 145 | } else if (line != QLatin1String("\r\n")) { 146 | int i = line.indexOf(QLatin1Char(':')); 147 | if (i == -1) { 148 | qWarning("Invalid HTTP request header"); 149 | m_socket->close(); 150 | return; 151 | } 152 | const QString key = line.left(i).trimmed(); 153 | const QString value = line.mid(i + 1).trimmed(); 154 | m_requestHeaders.append(qMakePair(key, value)); 155 | 156 | if (key.toLower() == QLatin1String("content-length")) { 157 | m_requestBytesRemaining = value.toInt(); 158 | } 159 | } else { 160 | if (m_requestBytesRemaining < 0 || m_requestBytesRemaining > MAX_BODY_SIZE) { 161 | qWarning("Invalid Content-Length"); 162 | m_socket->close(); 163 | return; 164 | } 165 | m_requestHeaderReceived = true; 166 | } 167 | } 168 | if (!m_requestHeaderReceived) { 169 | m_pendingRequest = request; 170 | return; 171 | } 172 | 173 | // Read request body 174 | if (m_requestBytesRemaining > 0) { 175 | const QByteArray chunk = m_socket->read(m_requestBytesRemaining); 176 | request->d->buffer += chunk; 177 | m_requestBytesRemaining -= chunk.size(); 178 | } 179 | if (m_requestBytesRemaining) { 180 | m_pendingRequest = request; 181 | return; 182 | } 183 | m_pendingRequest = 0; 184 | 185 | #ifdef QDJANGO_DEBUG_HTTP 186 | qDebug("Handling request %i", d->requestCount++); 187 | #endif 188 | 189 | /* Map meta-information */ 190 | QString metaKey; 191 | QList >::ConstIterator it = m_requestHeaders.constBegin(); 192 | while (it != m_requestHeaders.constEnd()) { 193 | if (it->first == QLatin1String("Content-Length")) 194 | metaKey = QLatin1String("CONTENT_LENGTH"); 195 | else if (it->first == QLatin1String("Content-Type")) 196 | metaKey = QLatin1String("CONTENT_TYPE"); 197 | else { 198 | metaKey = QLatin1String("HTTP_") + it->first.toUpper(); 199 | metaKey.replace(QLatin1Char('-'), QLatin1Char('_')); 200 | } 201 | request->d->meta.insert(metaKey, it->second); 202 | ++it; 203 | } 204 | 205 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) 206 | request->d->meta.insert(QLatin1String("QUERY_STRING"), QUrl(m_requestPath).query()); 207 | #else 208 | request->d->meta.insert(QLatin1String("QUERY_STRING"), QString::fromLatin1(QUrl(m_requestPath).encodedQuery())); 209 | #endif 210 | request->d->meta.insert(QLatin1String("REMOTE_ADDR"), m_socket->peerAddress().toString()); 211 | request->d->meta.insert(QLatin1String("REQUEST_METHOD"), request->method()); 212 | request->d->meta.insert(QLatin1String("SERVER_NAME"), m_socket->localAddress().toString()); 213 | request->d->meta.insert(QLatin1String("SERVER_PORT"), QString::number(m_socket->localPort())); 214 | 215 | /* Process request */ 216 | bool keepAlive = m_requestMajorVersion >= 1 && m_requestMinorVersion >= 1; 217 | if (request->d->meta.value(QLatin1String("HTTP_CONNECTION")).toLower() == QLatin1String("keep-alive")) 218 | keepAlive = true; 219 | else if (request->d->meta.value(QLatin1String("HTTP_CONNECTION")).toLower() == QLatin1String("close")) 220 | keepAlive = false; 221 | 222 | QDjangoHttpResponse *response = m_server->urls()->respond(*request, request->path()); 223 | m_pendingJobs << qMakePair(request, response); 224 | 225 | /* Store keep-alive flag */ 226 | if (!keepAlive) 227 | m_closeAfterResponse = true; 228 | 229 | connect(response, SIGNAL(ready()), this, SLOT(_q_writeResponse())); 230 | _q_writeResponse(); 231 | } 232 | 233 | void QDjangoHttpConnection::_q_writeResponse() 234 | { 235 | while (!m_pendingJobs.isEmpty() && 236 | m_pendingJobs.first().second->isReady()) { 237 | const QDjangoHttpJob job = m_pendingJobs.takeFirst(); 238 | QDjangoHttpRequest *request = job.first; 239 | QDjangoHttpResponse *response = job.second; 240 | if (!response->isReady()) 241 | return; 242 | 243 | /* Finalise response */ 244 | response->setHeader(QLatin1String("Date"), QDjangoHttpController::httpDateTime(QDateTime::currentDateTime())); 245 | response->setHeader(QLatin1String("Server"), QString::fromLatin1("%1/%2").arg(qApp->applicationName(), qApp->applicationVersion())); 246 | response->setHeader(QLatin1String("Connection"), QLatin1String(m_closeAfterResponse ? "close" : "keep-alive")); 247 | 248 | /* Send response */ 249 | QString httpHeader = QString::fromLatin1("HTTP/1.1 %1 %2\r\n").arg(response->d->statusCode).arg(response->d->reasonPhrase); 250 | QList >::ConstIterator it = response->d->headers.constBegin(); 251 | while (it != response->d->headers.constEnd()) { 252 | httpHeader += (*it).first + QLatin1String(": ") + (*it).second + QLatin1String("\r\n"); 253 | ++it; 254 | } 255 | m_socket->write(httpHeader.toUtf8() + "\r\n" + response->d->body); 256 | 257 | /* Emit signal */ 258 | emit requestFinished(request, response); 259 | 260 | /* Destroy response */ 261 | delete request; 262 | response->deleteLater(); 263 | } 264 | } 265 | 266 | /// \endcond 267 | 268 | class QDjangoHttpServerPrivate 269 | { 270 | public: 271 | int connectionCount; 272 | QTcpServer *tcpServer; 273 | QDjangoUrlResolver *urlResolver; 274 | }; 275 | 276 | /** Constructs a new HTTP server. 277 | */ 278 | QDjangoHttpServer::QDjangoHttpServer(QObject *parent) 279 | : QObject(parent), 280 | d(new QDjangoHttpServerPrivate) 281 | { 282 | d->connectionCount = 0; 283 | d->tcpServer = 0; 284 | d->urlResolver = new QDjangoUrlResolver(this); 285 | } 286 | 287 | /** Destroys the HTTP server. 288 | */ 289 | QDjangoHttpServer::~QDjangoHttpServer() 290 | { 291 | delete d; 292 | } 293 | 294 | /** Closes the server. The server will no longer listen for 295 | * incoming connections. 296 | */ 297 | void QDjangoHttpServer::close() 298 | { 299 | if (d->tcpServer) 300 | d->tcpServer->close(); 301 | } 302 | 303 | /** Tells the server to listen for incoming TCP connections on the given 304 | * \a address and \a port. 305 | */ 306 | bool QDjangoHttpServer::listen(const QHostAddress &address, quint16 port) 307 | { 308 | if (!d->tcpServer) { 309 | bool check; 310 | Q_UNUSED(check); 311 | 312 | d->tcpServer = new QTcpServer(this); 313 | check = connect(d->tcpServer, SIGNAL(newConnection()), 314 | this, SLOT(_q_newTcpConnection())); 315 | Q_ASSERT(check); 316 | } 317 | 318 | return d->tcpServer->listen(address, port); 319 | } 320 | 321 | /** Returns the server's address if the server is listening for connections; 322 | * otherwise returns QHostAddress::Null. 323 | */ 324 | QHostAddress QDjangoHttpServer::serverAddress() const 325 | { 326 | if (!d->tcpServer) 327 | return QHostAddress::Null; 328 | return d->tcpServer->serverAddress(); 329 | } 330 | 331 | /** Returns the server's port if the server is listening for connections; 332 | * otherwise returns 0. 333 | */ 334 | quint16 QDjangoHttpServer::serverPort() const 335 | { 336 | if (!d->tcpServer) 337 | return 0; 338 | return d->tcpServer->serverPort(); 339 | } 340 | 341 | /** Returns the root URL resolver for the server, which dispatches 342 | * requests to handlers. 343 | */ 344 | QDjangoUrlResolver* QDjangoHttpServer::urls() const 345 | { 346 | return d->urlResolver; 347 | } 348 | 349 | /** Handles the creation of new HTTP connections. 350 | */ 351 | void QDjangoHttpServer::_q_newTcpConnection() 352 | { 353 | bool check; 354 | Q_UNUSED(check); 355 | 356 | QTcpSocket *socket; 357 | while ((socket = d->tcpServer->nextPendingConnection()) != 0) { 358 | QDjangoHttpConnection *connection = new QDjangoHttpConnection(socket, this); 359 | #ifdef QDJANGO_DEBUG_HTTP 360 | qDebug("Handling connection %i", d->connectionCount++); 361 | #endif 362 | 363 | check = connect(connection, SIGNAL(closed()), 364 | connection, SLOT(deleteLater())); 365 | Q_ASSERT(check); 366 | 367 | check = connect(connection, SIGNAL(requestFinished(QDjangoHttpRequest*,QDjangoHttpResponse*)), 368 | this, SIGNAL(requestFinished(QDjangoHttpRequest*,QDjangoHttpResponse*))); 369 | Q_ASSERT(check); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpServer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_HTTP_SERVER_H 19 | #define QDJANGO_HTTP_SERVER_H 20 | 21 | #include 22 | #include 23 | 24 | #include "QDjangoHttp_p.h" 25 | 26 | class QDjangoHttpRequest; 27 | class QDjangoHttpResponse; 28 | class QDjangoHttpServer; 29 | class QDjangoHttpServerPrivate; 30 | class QDjangoUrlResolver; 31 | 32 | /** \brief The QDjangoHttpServer class represents an HTTP server. 33 | * 34 | * It allows you to create a standalone HTTP server which will 35 | * serve your web application. 36 | * 37 | * To register views, see urls(). 38 | * 39 | * \ingroup Http 40 | * \sa QDjangoFastCgiServer 41 | */ 42 | class QDJANGO_HTTP_EXPORT QDjangoHttpServer : public QObject 43 | { 44 | Q_OBJECT 45 | 46 | public: 47 | QDjangoHttpServer(QObject *parent = 0); 48 | ~QDjangoHttpServer(); 49 | 50 | void close(); 51 | bool listen(const QHostAddress &address, quint16 port); 52 | QHostAddress serverAddress() const; 53 | quint16 serverPort() const; 54 | QDjangoUrlResolver *urls() const; 55 | 56 | signals: 57 | /** This signal is emitted when a request completes. 58 | */ 59 | void requestFinished(QDjangoHttpRequest *request, QDjangoHttpResponse *response); 60 | 61 | private slots: 62 | void _q_newTcpConnection(); 63 | 64 | private: 65 | Q_DISABLE_COPY(QDjangoHttpServer) 66 | QDjangoHttpServerPrivate* const d; 67 | }; 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /src/http/QDjangoHttpServer_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_HTTP_SERVER_P_H 19 | #define QDJANGO_HTTP_SERVER_P_H 20 | 21 | // 22 | // W A R N I N G 23 | // ------------- 24 | // 25 | // This file is not part of the QDjango API. 26 | // 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | class QDjangoHttpRequest; 34 | class QDjangoHttpResponse; 35 | class QDjangoHttpServer; 36 | class QTcpSocket; 37 | 38 | typedef QPair QDjangoHttpJob; 39 | 40 | /** \internal 41 | */ 42 | class QDjangoHttpConnection : public QObject 43 | { 44 | Q_OBJECT 45 | 46 | public: 47 | QDjangoHttpConnection(QTcpSocket *device, QDjangoHttpServer *server); 48 | ~QDjangoHttpConnection(); 49 | 50 | signals: 51 | /** This signal is emitted when the connection is closed. 52 | */ 53 | void closed(); 54 | 55 | /** This signal is emitted when a request completes. 56 | */ 57 | void requestFinished(QDjangoHttpRequest *request, QDjangoHttpResponse *response); 58 | 59 | private slots: 60 | void _q_bytesWritten(qint64 bytes); 61 | void _q_readyRead(); 62 | void _q_writeResponse(); 63 | 64 | private: 65 | Q_DISABLE_COPY(QDjangoHttpConnection) 66 | bool m_closeAfterResponse; 67 | QList m_pendingJobs; 68 | QDjangoHttpRequest *m_pendingRequest; 69 | int m_requestCount; 70 | QDjangoHttpServer *m_server; 71 | QTcpSocket *m_socket; 72 | 73 | // request parsing 74 | qint64 m_requestBytesRemaining; 75 | int m_requestHeaderLine; 76 | bool m_requestHeaderReceived; 77 | QList > m_requestHeaders; 78 | int m_requestMajorVersion; 79 | int m_requestMinorVersion; 80 | QString m_requestPath; 81 | }; 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /src/http/QDjangoHttp_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_HTTP_P_H 19 | #define QDJANGO_HTTP_P_H 20 | 21 | #if defined(QDJANGO_SHARED) 22 | # if defined(QDJANGO_HTTP_BUILD) 23 | # define QDJANGO_HTTP_EXPORT Q_DECL_EXPORT 24 | # define QDJANGO_HTTP_AUTOTEST_EXPORT Q_DECL_EXPORT 25 | # else 26 | # define QDJANGO_HTTP_EXPORT Q_DECL_IMPORT 27 | # define QDJANGO_HTTP_AUTOTEST_EXPORT Q_DECL_IMPORT 28 | # endif 29 | #else 30 | # define QDJANGO_HTTP_EXPORT 31 | # define QDJANGO_HTTP_AUTOTEST_EXPORT 32 | #endif 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/http/QDjangoUrlResolver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "QDjangoHttpController.h" 24 | #include "QDjangoHttpRequest.h" 25 | #include "QDjangoHttpResponse.h" 26 | #include "QDjangoUrlResolver.h" 27 | 28 | class QDjangoUrlResolverRoute { 29 | public: 30 | QDjangoUrlResolverRoute() 31 | : receiver(0) 32 | , urls(0) 33 | { 34 | } 35 | 36 | QRegExp path; 37 | QObject *receiver; 38 | QByteArray member; 39 | QDjangoUrlResolver *urls; 40 | }; 41 | 42 | class QDjangoUrlResolverPrivate 43 | { 44 | public: 45 | QDjangoHttpResponse* respond(const QDjangoHttpRequest &request, const QString &path) const; 46 | QString reverse(QObject *receiver, const char *member, const QVariantList &args = QVariantList()) const; 47 | 48 | QList routes; 49 | }; 50 | 51 | QDjangoHttpResponse* QDjangoUrlResolverPrivate::respond(const QDjangoHttpRequest &request, const QString &path) const 52 | { 53 | QList::const_iterator it; 54 | for (it = routes.constBegin(); it != routes.constEnd(); ++it) { 55 | if (it->urls && it->path.indexIn(path) == 0) { 56 | // try recursing 57 | QString subPath = path.mid(it->path.capturedTexts().first().size()); 58 | QDjangoHttpResponse *response = it->urls->d->respond(request, subPath); 59 | if (response) 60 | return response; 61 | } else if (it->receiver && it->path.exactMatch(path)) { 62 | // collect arguments 63 | QStringList caps = it->path.capturedTexts(); 64 | caps.takeFirst(); 65 | QList args; 66 | args << Q_ARG(QDjangoHttpRequest, request); 67 | for (int i = 0; i < caps.size(); ++i) { 68 | args << Q_ARG(QString, caps[i]); 69 | } 70 | while (args.size() < 10) { 71 | args << QGenericArgument(); 72 | } 73 | 74 | QDjangoHttpResponse *response = 0; 75 | if (!QMetaObject::invokeMethod(it->receiver, it->member.constData(), Qt::DirectConnection, 76 | Q_RETURN_ARG(QDjangoHttpResponse*, response), 77 | args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]) 78 | || !response) { 79 | return QDjangoHttpController::serveInternalServerError(request); 80 | } 81 | return response; 82 | } 83 | } 84 | return 0; 85 | } 86 | 87 | QString QDjangoUrlResolverPrivate::reverse(QObject *receiver, const char *member, const QVariantList &args) const 88 | { 89 | QList::const_iterator it; 90 | for (it = routes.constBegin(); it != routes.constEnd(); ++it) { 91 | // recurse 92 | if (it->urls) { 93 | QString path = it->urls->d->reverse(receiver, member, args); 94 | if (!path.isNull()) { 95 | QString prefix = it->path.pattern(); 96 | if (prefix.startsWith(QLatin1Char('^'))) 97 | prefix.remove(0, 1); 98 | if (prefix.endsWith(QLatin1Char('$'))) 99 | prefix.chop(1); 100 | 101 | return prefix + path; 102 | } 103 | } else if (it->receiver == receiver && it->member == member) { 104 | QString path = it->path.pattern(); 105 | if (path.startsWith(QLatin1Char('^'))) 106 | path.remove(0, 1); 107 | if (path.endsWith(QLatin1Char('$'))) 108 | path.chop(1); 109 | 110 | // replace parameters 111 | QVariantList arguments(args); 112 | int pos = 0; 113 | QRegExp rx(QLatin1String("\\([^)]+\\)")); 114 | while ((pos = rx.indexIn(path, pos)) != -1) { 115 | if (arguments.isEmpty()) { 116 | qWarning("Too few arguments for '%s'", member); 117 | return QString(); 118 | } 119 | const QString str = arguments.takeFirst().toString(); 120 | path.replace(pos, rx.matchedLength(), str); 121 | pos += str.size(); 122 | } 123 | if (!arguments.isEmpty()) { 124 | qWarning("Too many arguments for '%s'", member); 125 | return QString(); 126 | } 127 | if (path.isEmpty()) 128 | return QLatin1String(""); 129 | else 130 | return path; 131 | } 132 | } 133 | 134 | // not found 135 | return QString(); 136 | } 137 | 138 | /** Constructs a new URL resolver with the given \a parent. 139 | */ 140 | QDjangoUrlResolver::QDjangoUrlResolver(QObject *parent) 141 | : QObject(parent) 142 | , d(new QDjangoUrlResolverPrivate) 143 | { 144 | } 145 | 146 | QDjangoUrlResolver::~QDjangoUrlResolver() 147 | { 148 | delete d; 149 | } 150 | 151 | /** Adds a URL mapping for the given \a path. 152 | */ 153 | bool QDjangoUrlResolver::set(const QRegExp &path, QObject *receiver, const char *member) 154 | { 155 | Q_ASSERT(receiver); 156 | Q_ASSERT(member); 157 | 158 | const QMetaObject *metaObject = receiver->metaObject(); 159 | QByteArray needle(member); 160 | needle += '('; 161 | for (int i = metaObject->methodOffset(); i < metaObject->methodCount(); ++i) { 162 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) 163 | if (metaObject->method(i).name() == member) { 164 | #else 165 | const QByteArray signature = metaObject->method(i).signature(); 166 | if (signature.startsWith(needle)) { 167 | #endif 168 | 169 | // check parameter types 170 | const QList ptypes = metaObject->method(i).parameterTypes(); 171 | if (ptypes.isEmpty() || ptypes[0] != "QDjangoHttpRequest") { 172 | qWarning("First argument of '%s' should be a QDjangoHttpRequest", member); 173 | return false; 174 | } 175 | 176 | // register route 177 | QDjangoUrlResolverRoute route; 178 | route.path = path; 179 | route.receiver = receiver; 180 | route.member = member; 181 | d->routes << route; 182 | return true; 183 | } 184 | } 185 | 186 | qWarning("Could not find '%s' in receiver", member); 187 | return false; 188 | } 189 | 190 | /** Adds a URL mapping for the given \a path. 191 | */ 192 | bool QDjangoUrlResolver::include(const QRegExp &path, QDjangoUrlResolver *urls) 193 | { 194 | Q_ASSERT(urls); 195 | 196 | // register route 197 | QDjangoUrlResolverRoute route; 198 | route.path = path; 199 | route.urls = urls; 200 | d->routes << route; 201 | return true; 202 | } 203 | 204 | /** Responds to the given HTTP \a request for the given \a path. 205 | */ 206 | QDjangoHttpResponse* QDjangoUrlResolver::respond(const QDjangoHttpRequest &request, const QString &path) const 207 | { 208 | QString fixedPath(path); 209 | if (fixedPath.startsWith(QLatin1Char('/'))) 210 | fixedPath.remove(0, 1); 211 | 212 | QDjangoHttpResponse *response = d->respond(request, fixedPath); 213 | if (response) 214 | return response; 215 | else 216 | return QDjangoHttpController::serveNotFound(request); 217 | } 218 | 219 | /** Returns the URL for the member \a member of \a receiver with 220 | * \a args as arguments. 221 | */ 222 | QString QDjangoUrlResolver::reverse(QObject *receiver, const char *member, const QVariantList &args) const 223 | { 224 | QString path = d->reverse(receiver, member, args); 225 | if (path.isNull()) 226 | return QString(); 227 | else 228 | return QLatin1String("/") + path; 229 | } 230 | 231 | 232 | -------------------------------------------------------------------------------- /src/http/QDjangoUrlResolver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_URL_RESOLVER_H 19 | #define QDJANGO_URL_RESOLVER_H 20 | 21 | #include 22 | #include 23 | 24 | #include "QDjangoHttp_p.h" 25 | 26 | class QDjangoHttpRequest; 27 | class QDjangoHttpResponse; 28 | class QDjangoUrlResolverPrivate; 29 | class QRegExp; 30 | 31 | /** \brief The QDjangoUrlResolver class maps incoming HTTP requests to handlers. 32 | * 33 | * \ingroup Http 34 | */ 35 | class QDJANGO_HTTP_EXPORT QDjangoUrlResolver : public QObject 36 | { 37 | Q_OBJECT 38 | 39 | public: 40 | QDjangoUrlResolver(QObject *parent = 0); 41 | ~QDjangoUrlResolver(); 42 | 43 | bool include(const QRegExp &path, QDjangoUrlResolver *urls); 44 | bool set(const QRegExp &path, QObject *receiver, const char *member); 45 | QString reverse(QObject *receiver, const char *member, const QVariantList &args = QVariantList()) const; 46 | 47 | public slots: 48 | QDjangoHttpResponse* respond(const QDjangoHttpRequest &request, const QString &path) const; 49 | 50 | private: 51 | QDjangoUrlResolverPrivate *d; 52 | friend class QDjangoUrlResolverPrivate; 53 | }; 54 | 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/http/http.pro: -------------------------------------------------------------------------------- 1 | include(../../qdjango.pri) 2 | 3 | QT -= gui 4 | QT += network 5 | 6 | DEFINES += QDJANGO_HTTP_BUILD 7 | 8 | TARGET = qdjango-http 9 | win32 { 10 | DESTDIR = $$OUT_PWD 11 | } 12 | 13 | HEADERS += \ 14 | QDjangoFastCgiServer.h \ 15 | QDjangoFastCgiServer_p.h \ 16 | QDjangoHttp_p.h \ 17 | QDjangoHttpController.h \ 18 | QDjangoHttpRequest.h \ 19 | QDjangoHttpResponse.h \ 20 | QDjangoHttpServer.h \ 21 | QDjangoHttpServer_p.h \ 22 | QDjangoUrlResolver.h 23 | SOURCES += \ 24 | QDjangoFastCgiServer.cpp \ 25 | QDjangoHttpController.cpp \ 26 | QDjangoHttpRequest.cpp \ 27 | QDjangoHttpResponse.cpp \ 28 | QDjangoHttpServer.cpp \ 29 | QDjangoUrlResolver.cpp 30 | 31 | # Installation 32 | include(../src.pri) 33 | headers.path = $$PREFIX/include/qdjango/http 34 | QMAKE_PKGCONFIG_INCDIR = $$headers.path 35 | -------------------------------------------------------------------------------- /src/src.pri: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += $$QDJANGO_LIBRARY_TYPE 3 | VERSION = $$QDJANGO_VERSION 4 | 5 | # Installation 6 | headers.files = $$HEADERS 7 | target.path = $$PREFIX/$$LIBDIR 8 | INSTALLS += headers target 9 | 10 | # pkg-config support 11 | CONFIG += create_pc create_prl no_install_prl 12 | QMAKE_PKGCONFIG_DESTDIR = pkgconfig 13 | QMAKE_PKGCONFIG_LIBDIR = $$target.path 14 | equals(QDJANGO_LIBRARY_TYPE,staticlib) { 15 | QMAKE_PKGCONFIG_CFLAGS = -DQDJANGO_STATIC 16 | } else { 17 | QMAKE_PKGCONFIG_CFLAGS = -DQDJANGO_SHARED 18 | } 19 | unix:QMAKE_CLEAN += -r pkgconfig lib$${TARGET}.prl 20 | 21 | # profiling support 22 | equals(QDJANGO_PROFILE,true) { 23 | QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage 24 | QMAKE_LIBS += -lgcov 25 | QMAKE_CLEAN += *.gcda *.gcov *.gcno 26 | } 27 | -------------------------------------------------------------------------------- /src/src.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS = db http 4 | 5 | CONFIG += ordered 6 | -------------------------------------------------------------------------------- /tests/db/auth-models.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include "QDjangoQuerySet.h" 19 | 20 | #include "auth-models.h" 21 | 22 | User::User(QObject *parent) 23 | : QDjangoModel(parent), 24 | m_isActive(true), 25 | m_isStaff(false), 26 | m_isSuperUser(false) 27 | { 28 | // initialise dates 29 | m_dateJoined = QDateTime::currentDateTime(); 30 | m_lastLogin = QDateTime::currentDateTime(); 31 | } 32 | 33 | QString User::username() const 34 | { 35 | return m_username; 36 | } 37 | 38 | void User::setUsername(const QString &username) 39 | { 40 | m_username = username; 41 | } 42 | 43 | QString User::firstName() const 44 | { 45 | return m_firstName; 46 | } 47 | 48 | void User::setFirstName(const QString &firstName) 49 | { 50 | m_firstName = firstName; 51 | } 52 | 53 | QString User::lastName() const 54 | { 55 | return m_lastName; 56 | } 57 | 58 | void User::setLastName(const QString &lastName) 59 | { 60 | m_lastName = lastName; 61 | } 62 | 63 | QString User::email() const 64 | { 65 | return m_email; 66 | } 67 | 68 | void User::setEmail(const QString &email) 69 | { 70 | m_email = email; 71 | } 72 | 73 | QString User::password() const 74 | { 75 | return m_password; 76 | } 77 | 78 | void User::setPassword(const QString &password) 79 | { 80 | m_password = password; 81 | } 82 | 83 | bool User::isActive() const 84 | { 85 | return m_isActive; 86 | } 87 | 88 | void User::setIsActive(bool isActive) 89 | { 90 | m_isActive = isActive; 91 | } 92 | 93 | bool User::isStaff() const 94 | { 95 | return m_isStaff; 96 | } 97 | 98 | void User::setIsStaff(bool isStaff) 99 | { 100 | m_isStaff = isStaff; 101 | } 102 | 103 | bool User::isSuperUser() const 104 | { 105 | return m_isSuperUser; 106 | } 107 | 108 | void User::setIsSuperUser(bool isSuperUser) 109 | { 110 | m_isSuperUser = isSuperUser; 111 | } 112 | 113 | QDateTime User::dateJoined() const 114 | { 115 | return m_dateJoined; 116 | } 117 | 118 | void User::setDateJoined(const QDateTime &dateJoined) 119 | { 120 | m_dateJoined = dateJoined; 121 | } 122 | 123 | QDateTime User::lastLogin() const 124 | { 125 | return m_lastLogin; 126 | } 127 | 128 | void User::setLastLogin(const QDateTime &lastLogin) 129 | { 130 | m_lastLogin = lastLogin; 131 | } 132 | 133 | Group::Group(QObject *parent) 134 | : QDjangoModel(parent) 135 | { 136 | } 137 | 138 | QString Group::name() const 139 | { 140 | return m_name; 141 | } 142 | 143 | void Group::setName(const QString &name) 144 | { 145 | m_name = name; 146 | } 147 | 148 | UserGroups::UserGroups(QObject *parent) 149 | : QDjangoModel(parent) 150 | { 151 | setForeignKey("user", new User(this)); 152 | setForeignKey("group", new Group(this)); 153 | } 154 | 155 | User *UserGroups::user() const 156 | { 157 | return qobject_cast(foreignKey("user")); 158 | } 159 | 160 | void UserGroups::setUser(User *user) 161 | { 162 | setForeignKey("user", user); 163 | } 164 | 165 | Group *UserGroups::group() const 166 | { 167 | return qobject_cast(foreignKey("group")); 168 | } 169 | 170 | void UserGroups::setGroup(Group *group) 171 | { 172 | setForeignKey("group", group); 173 | } 174 | 175 | Message::Message(QObject *parent) 176 | : QDjangoModel(parent) 177 | { 178 | setForeignKey("user", new User(this)); 179 | } 180 | 181 | /** Returns the User associated with this Message. 182 | */ 183 | User *Message::user() const 184 | { 185 | return qobject_cast(foreignKey("user")); 186 | } 187 | 188 | void Message::setUser(User *user) 189 | { 190 | setForeignKey("user", user); 191 | } 192 | 193 | QString Message::message() const 194 | { 195 | return m_message; 196 | } 197 | 198 | void Message::setMessage(const QString &message) 199 | { 200 | m_message = message; 201 | } 202 | 203 | Q_DECLARE_METATYPE(Group*) 204 | Q_DECLARE_METATYPE(User*) 205 | -------------------------------------------------------------------------------- /tests/db/auth-models.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #ifndef QDJANGO_AUTH_MODELS_H 19 | #define QDJANGO_AUTH_MODELS_H 20 | 21 | #include 22 | 23 | #include "QDjangoModel.h" 24 | 25 | /** The User class represents a user in the authentication system. 26 | * 27 | * It has a many-to-many relationship with the Group class. 28 | */ 29 | class User : public QDjangoModel 30 | { 31 | Q_OBJECT 32 | Q_PROPERTY(QString username READ username WRITE setUsername) 33 | Q_PROPERTY(QString first_name READ firstName WRITE setFirstName) 34 | Q_PROPERTY(QString last_name READ lastName WRITE setLastName) 35 | Q_PROPERTY(QString email READ email WRITE setEmail) 36 | Q_PROPERTY(QString password READ password WRITE setPassword) 37 | Q_PROPERTY(bool is_active READ isActive WRITE setIsActive) 38 | Q_PROPERTY(bool is_staff READ isStaff WRITE setIsStaff) 39 | Q_PROPERTY(bool is_superuser READ isSuperUser WRITE setIsSuperUser) 40 | Q_PROPERTY(QDateTime date_joined READ dateJoined WRITE setDateJoined) 41 | Q_PROPERTY(QDateTime last_login READ lastLogin WRITE setLastLogin) 42 | 43 | Q_CLASSINFO("username", "max_length=30") 44 | Q_CLASSINFO("first_name", "max_length=30") 45 | Q_CLASSINFO("last_name", "max_length=30") 46 | Q_CLASSINFO("password", "max_length=128") 47 | 48 | public: 49 | User(QObject *parent = 0); 50 | 51 | QString username() const; 52 | void setUsername(const QString &username); 53 | 54 | QString firstName() const; 55 | void setFirstName(const QString &firstName); 56 | 57 | QString lastName() const; 58 | void setLastName(const QString &lastName); 59 | 60 | QString email() const; 61 | void setEmail(const QString &email); 62 | 63 | QString password() const; 64 | void setPassword(const QString &password); 65 | 66 | // flags 67 | bool isActive() const; 68 | void setIsActive(bool isActive); 69 | 70 | bool isStaff() const; 71 | void setIsStaff(bool isStaff); 72 | 73 | bool isSuperUser() const; 74 | void setIsSuperUser(bool isSuperUser); 75 | 76 | // dates 77 | QDateTime dateJoined() const; 78 | void setDateJoined(const QDateTime &dateJoined); 79 | 80 | QDateTime lastLogin() const; 81 | void setLastLogin(const QDateTime &lastLogin); 82 | 83 | private: 84 | QString m_username; 85 | QString m_firstName; 86 | QString m_lastName; 87 | QString m_email; 88 | QString m_password; 89 | bool m_isActive; 90 | bool m_isStaff; 91 | bool m_isSuperUser; 92 | QDateTime m_dateJoined; 93 | QDateTime m_lastLogin; 94 | }; 95 | 96 | /** The Group class represents a group in the authentication system. 97 | * 98 | * It has a many-to-many relationship with the User class. 99 | */ 100 | class Group : public QDjangoModel 101 | { 102 | Q_OBJECT 103 | Q_PROPERTY(QString name READ name WRITE setName) 104 | 105 | public: 106 | Group(QObject *parent = 0); 107 | QString name() const; 108 | void setName(const QString &name); 109 | 110 | private: 111 | QString m_name; 112 | }; 113 | 114 | class UserGroups : public QDjangoModel 115 | { 116 | Q_OBJECT 117 | Q_PROPERTY(User* user READ user WRITE setUser); 118 | Q_PROPERTY(Group* group READ group WRITE setGroup); 119 | 120 | public: 121 | UserGroups(QObject *parent = 0); 122 | 123 | User *user() const; 124 | void setUser(User *user); 125 | 126 | Group *group() const; 127 | void setGroup(Group *group); 128 | }; 129 | 130 | /** The Message class represents a message for a given User. 131 | */ 132 | class Message : public QDjangoModel 133 | { 134 | Q_OBJECT 135 | Q_PROPERTY(User* user READ user WRITE setUser); 136 | Q_PROPERTY(QString message READ message WRITE setMessage) 137 | 138 | public: 139 | Message(QObject *parent = 0); 140 | 141 | User *user() const; 142 | void setUser(User *user); 143 | 144 | QString message() const; 145 | void setMessage(const QString &message); 146 | 147 | private: 148 | QString m_message; 149 | }; 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /tests/db/auth/auth.pro: -------------------------------------------------------------------------------- 1 | include(../db.pri) 2 | 3 | TARGET = tst_auth 4 | HEADERS += ../auth-models.h 5 | SOURCES += ../auth-models.cpp tst_auth.cpp 6 | -------------------------------------------------------------------------------- /tests/db/db.pri: -------------------------------------------------------------------------------- 1 | include(../tests.pri) 2 | 3 | QT += sql 4 | 5 | HEADERS += $$PWD/util.h 6 | SOURCES += $$PWD/util.cpp 7 | 8 | INCLUDEPATH += $$PWD 9 | LIBS += -L../../../src/db $$QDJANGO_DB_LIBS 10 | -------------------------------------------------------------------------------- /tests/db/db.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS = \ 3 | qdjango \ 4 | qdjangocompiler \ 5 | qdjangometamodel \ 6 | qdjangomodel \ 7 | qdjangoqueryset \ 8 | qdjangowhere \ 9 | auth \ 10 | shares 11 | 12 | -------------------------------------------------------------------------------- /tests/db/qdjango/qdjango.pro: -------------------------------------------------------------------------------- 1 | include(../db.pri) 2 | TARGET = tst_qdjango 3 | SOURCES += tst_qdjango.cpp 4 | -------------------------------------------------------------------------------- /tests/db/qdjango/tst_qdjango.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "QDjango.h" 23 | #include "QDjangoModel.h" 24 | #include "QDjangoQuerySet.h" 25 | 26 | #include "util.h" 27 | 28 | class Author : public QDjangoModel 29 | { 30 | Q_OBJECT 31 | Q_PROPERTY(QString name READ name WRITE setName) 32 | 33 | public: 34 | Author(QObject *parent = 0) : QDjangoModel(parent) {} 35 | 36 | QString name() const { return m_name; } 37 | void setName(const QString &name) { m_name = name; } 38 | 39 | private: 40 | QString m_name; 41 | }; 42 | 43 | class Worker : public QObject 44 | { 45 | Q_OBJECT 46 | 47 | public slots: 48 | void doIt(); 49 | 50 | signals: 51 | void done(); 52 | }; 53 | 54 | void Worker::doIt() 55 | { 56 | Author author; 57 | author.setName("someone"); 58 | QVERIFY(author.save()); 59 | 60 | emit done(); 61 | } 62 | 63 | class tst_QDjango : public QObject 64 | { 65 | Q_OBJECT 66 | 67 | private slots: 68 | void initTestCase(); 69 | void init(); 70 | void databaseThreaded(); 71 | void debugEnabled(); 72 | void debugQuery(); 73 | void cleanup(); 74 | }; 75 | 76 | void tst_QDjango::initTestCase() 77 | { 78 | QCOMPARE(QDjango::database().isOpen(), false); 79 | QVERIFY(initialiseDatabase()); 80 | QCOMPARE(QDjango::database().isOpen(), true); 81 | } 82 | 83 | void tst_QDjango::init() 84 | { 85 | QDjango::registerModel(); 86 | 87 | QSqlDatabase db = QDjango::database(); 88 | QVERIFY(db.tables().indexOf("author") == -1); 89 | QVERIFY(QDjango::createTables()); 90 | QVERIFY(db.tables().indexOf("author") != -1); 91 | } 92 | 93 | void tst_QDjango::cleanup() 94 | { 95 | QSqlDatabase db = QDjango::database(); 96 | QVERIFY(QDjango::dropTables()); 97 | QVERIFY(db.tables().indexOf("author") == -1); 98 | } 99 | 100 | void tst_QDjango::databaseThreaded() 101 | { 102 | if (QDjango::database().databaseName() == QLatin1String(":memory:")) 103 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) 104 | QSKIP("Threaded test cannot work with in-memory SQLite database."); 105 | #else 106 | QSKIP("Threaded test cannot work with in-memory SQLite database.", SkipAll); 107 | #endif 108 | 109 | QDjangoQuerySet qs; 110 | QCOMPARE(qs.count(), 0); 111 | 112 | QEventLoop loop; 113 | Worker worker; 114 | QThread workerThread; 115 | 116 | // fire up worker 117 | worker.moveToThread(&workerThread); 118 | connect(&worker, SIGNAL(done()), &loop, SLOT(quit())); 119 | 120 | workerThread.start(); 121 | QTimer::singleShot(0, &worker, SLOT(doIt())); 122 | loop.exec(); 123 | 124 | // check database 125 | QCOMPARE(qs.count(), 1); 126 | 127 | // stop thread 128 | workerThread.quit(); 129 | workerThread.wait(); 130 | } 131 | 132 | void tst_QDjango::debugEnabled() 133 | { 134 | QCOMPARE(QDjango::isDebugEnabled(), false); 135 | QDjango::setDebugEnabled(true); 136 | QCOMPARE(QDjango::isDebugEnabled(), true); 137 | QDjango::setDebugEnabled(false); 138 | QCOMPARE(QDjango::isDebugEnabled(), false); 139 | } 140 | 141 | void tst_QDjango::debugQuery() 142 | { 143 | QDjangoQuery query(QDjango::database()); 144 | QDjango::setDebugEnabled(true); 145 | QVERIFY(!query.exec("SELECT foo")); 146 | QDjango::setDebugEnabled(false); 147 | } 148 | 149 | QTEST_MAIN(tst_QDjango) 150 | #include "tst_qdjango.moc" 151 | -------------------------------------------------------------------------------- /tests/db/qdjangocompiler/qdjangocompiler.pro: -------------------------------------------------------------------------------- 1 | include(../db.pri) 2 | TARGET = tst_qdjangocompiler 3 | SOURCES += tst_qdjangocompiler.cpp 4 | -------------------------------------------------------------------------------- /tests/db/qdjangometamodel/qdjangometamodel.pro: -------------------------------------------------------------------------------- 1 | include(../db.pri) 2 | 3 | TARGET = tst_qdjangometamodel 4 | HEADERS += ../auth-models.h tst_qdjangometamodel.h 5 | SOURCES += ../auth-models.cpp tst_qdjangometamodel.cpp 6 | -------------------------------------------------------------------------------- /tests/db/qdjangometamodel/tst_qdjangometamodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | 20 | #include "QDjangoModel.h" 21 | 22 | #include "auth-models.h" 23 | 24 | class tst_QDjangoMetaModel : public QObject 25 | { 26 | Q_OBJECT 27 | 28 | private slots: 29 | void initTestCase(); 30 | void testBool(); 31 | void testByteArray(); 32 | void testDate(); 33 | void testDateTime(); 34 | void testDouble(); 35 | void testInteger(); 36 | void testLongLong(); 37 | void testString(); 38 | void testTime(); 39 | void testOptions(); 40 | void testConstraints(); 41 | void testIsValid(); 42 | }; 43 | 44 | class tst_Bool : public QDjangoModel 45 | { 46 | Q_OBJECT 47 | Q_PROPERTY(bool value READ value WRITE setValue) 48 | 49 | public: 50 | bool value() const { return m_value; } 51 | void setValue(bool value) { m_value = value; } 52 | 53 | private: 54 | bool m_value; 55 | }; 56 | 57 | class tst_ByteArray : public QDjangoModel 58 | { 59 | Q_OBJECT 60 | Q_PROPERTY(QByteArray value READ value WRITE setValue) 61 | 62 | public: 63 | QByteArray value() const { return m_value; } 64 | void setValue(const QByteArray &value) { m_value = value; } 65 | 66 | private: 67 | QByteArray m_value; 68 | }; 69 | 70 | class tst_Date : public QDjangoModel 71 | { 72 | Q_OBJECT 73 | Q_PROPERTY(QDate value READ value WRITE setValue) 74 | 75 | public: 76 | QDate value() const { return m_value; } 77 | void setValue(const QDate &value) { m_value = value; } 78 | 79 | private: 80 | QDate m_value; 81 | }; 82 | 83 | class tst_DateTime : public QDjangoModel 84 | { 85 | Q_OBJECT 86 | Q_PROPERTY(QDateTime value READ value WRITE setValue) 87 | 88 | public: 89 | QDateTime value() const { return m_value; } 90 | void setValue(const QDateTime &value) { m_value = value; } 91 | 92 | private: 93 | QDateTime m_value; 94 | }; 95 | 96 | class tst_Double : public QDjangoModel 97 | { 98 | Q_OBJECT 99 | Q_PROPERTY(double value READ value WRITE setValue) 100 | 101 | public: 102 | double value() const { return m_value; } 103 | void setValue(double value) { m_value = value; } 104 | 105 | private: 106 | double m_value; 107 | }; 108 | 109 | class tst_Integer : public QDjangoModel 110 | { 111 | Q_OBJECT 112 | Q_PROPERTY(int value READ value WRITE setValue) 113 | 114 | public: 115 | int value() const { return m_value; } 116 | void setValue(int value) { m_value = value; } 117 | 118 | private: 119 | int m_value; 120 | }; 121 | 122 | class tst_LongLong : public QDjangoModel 123 | { 124 | Q_OBJECT 125 | Q_PROPERTY(qlonglong value READ value WRITE setValue) 126 | 127 | public: 128 | qlonglong value() const { return m_value; } 129 | void setValue(qlonglong value) { m_value = value; } 130 | 131 | private: 132 | qlonglong m_value; 133 | }; 134 | 135 | class tst_String : public QDjangoModel 136 | { 137 | Q_OBJECT 138 | Q_PROPERTY(QString value READ value WRITE setValue) 139 | Q_CLASSINFO("value", "max_length=255") 140 | 141 | public: 142 | QString value() const { return m_value; } 143 | void setValue(const QString &value) { m_value = value; } 144 | 145 | private: 146 | QString m_value; 147 | }; 148 | 149 | class tst_Time : public QDjangoModel 150 | { 151 | Q_OBJECT 152 | Q_PROPERTY(QTime value READ value WRITE setValue) 153 | 154 | public: 155 | QTime value() const { return m_value; } 156 | void setValue(const QTime &value) { m_value = value; } 157 | 158 | private: 159 | QTime m_value; 160 | }; 161 | 162 | class tst_Options : public QDjangoModel 163 | { 164 | Q_OBJECT 165 | Q_PROPERTY(int aField READ aField WRITE setAField) 166 | Q_PROPERTY(int bField READ bField WRITE setBField) 167 | Q_PROPERTY(int blankField READ blankField WRITE setBlankField) 168 | Q_PROPERTY(int ignoredField READ ignoredField WRITE setIgnoredField) 169 | Q_PROPERTY(int indexField READ indexField WRITE setIndexField) 170 | Q_PROPERTY(int nullField READ nullField WRITE setNullField) 171 | Q_PROPERTY(int uniqueField READ uniqueField WRITE setUniqueField) 172 | 173 | Q_CLASSINFO("__meta__", "db_table=some_table unique_together=aField,bField") 174 | Q_CLASSINFO("bField", "db_column=b_field") 175 | Q_CLASSINFO("blankField", "blank=true") 176 | Q_CLASSINFO("ignoredField", "ignore_field=true") 177 | Q_CLASSINFO("indexField", "db_index=true") 178 | Q_CLASSINFO("nullField", "null=true") 179 | Q_CLASSINFO("uniqueField", "unique=true") 180 | 181 | public: 182 | int aField() const { return m_aField; } 183 | void setAField(int value) { m_aField = value; } 184 | 185 | int bField() const { return m_bField; } 186 | void setBField(int value) { m_bField = value; } 187 | 188 | int blankField() const { return m_blankField; } 189 | void setBlankField(int value) { m_blankField = value; } 190 | 191 | int ignoredField() const { return m_ignoredField; } 192 | void setIgnoredField(int value) { m_ignoredField = value; } 193 | 194 | int indexField() const { return m_indexField; } 195 | void setIndexField(int value) { m_indexField = value; } 196 | 197 | int nullField() const { return m_nullField; } 198 | void setNullField(int value) { m_nullField = value; } 199 | 200 | int uniqueField() const { return m_uniqueField; } 201 | void setUniqueField(int value) { m_uniqueField = value; } 202 | 203 | private: 204 | int m_aField; 205 | int m_bField; 206 | int m_blankField; 207 | int m_ignoredField; 208 | int m_indexField; 209 | int m_nullField; 210 | int m_uniqueField; 211 | }; 212 | 213 | class tst_FkConstraint : public QDjangoModel 214 | { 215 | Q_OBJECT 216 | Q_PROPERTY(User *noConstraint READ noConstraint WRITE setNoConstraint) 217 | Q_PROPERTY(User *cascadeConstraint READ cascadeConstraint WRITE setCascadeConstraint) 218 | Q_PROPERTY(Group *nullConstraint READ nullConstraint WRITE setNullConstraint) 219 | 220 | Q_CLASSINFO("cascadeConstraint", "on_delete=cascade") 221 | Q_CLASSINFO("restrictConstraint", "on_delete=restrict") 222 | Q_CLASSINFO("nullConstraint", "null=true on_delete=set_null") 223 | 224 | public: 225 | tst_FkConstraint(QObject *parent = 0); 226 | 227 | User *noConstraint() const; 228 | void setNoConstraint(User *user); 229 | 230 | User *cascadeConstraint() const; 231 | void setCascadeConstraint(User *user); 232 | 233 | Group *nullConstraint() const; 234 | void setNullConstraint(Group *group); 235 | }; 236 | 237 | class tst_FkConstraintWithRestrict : public tst_FkConstraint 238 | { 239 | Q_OBJECT 240 | Q_PROPERTY(User *restrictConstraint READ restrictConstraint WRITE setRestrictConstraint) 241 | 242 | Q_CLASSINFO("__meta__", "db_table=tst_fkconstraint") 243 | Q_CLASSINFO("restrictConstraint", "on_delete=restrict") 244 | public: 245 | tst_FkConstraintWithRestrict(QObject *parent = 0); 246 | 247 | User *restrictConstraint() const; 248 | void setRestrictConstraint(User *user); 249 | }; 250 | -------------------------------------------------------------------------------- /tests/db/qdjangomodel/qdjangomodel.pro: -------------------------------------------------------------------------------- 1 | include(../db.pri) 2 | 3 | TARGET = tst_qdjangomodel 4 | SOURCES += tst_qdjangomodel.cpp 5 | -------------------------------------------------------------------------------- /tests/db/qdjangomodel/tst_qdjangomodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include "QDjango.h" 19 | #include "QDjangoModel.h" 20 | #include "QDjangoQuerySet.h" 21 | #include "QDjangoWhere.h" 22 | #include "util.h" 23 | 24 | class TestModel : public QDjangoModel 25 | { 26 | public: 27 | TestModel(QObject *parent = 0) : QDjangoModel(parent) {} 28 | 29 | // expose foreign key methods so they can be tested 30 | QObject *foreignKey(const char *name) const 31 | { return QDjangoModel::foreignKey(name); } 32 | void setForeignKey(const char *name, QObject *value) 33 | { QDjangoModel::setForeignKey(name, value); } 34 | }; 35 | 36 | class Author : public TestModel 37 | { 38 | Q_OBJECT 39 | Q_PROPERTY(QString name READ name WRITE setName) 40 | 41 | public: 42 | Author(QObject *parent = 0) : TestModel(parent) {} 43 | 44 | QString name() const { return m_name; } 45 | void setName(const QString &name) { m_name = name; } 46 | 47 | private: 48 | QString m_name; 49 | }; 50 | 51 | class Book : public TestModel 52 | { 53 | Q_OBJECT 54 | Q_PROPERTY(Author* author READ author WRITE setAuthor) 55 | Q_PROPERTY(QString title READ title WRITE setTitle) 56 | Q_CLASSINFO("author", "on_delete=cascade") 57 | 58 | public: 59 | Book(QObject *parent = 0) 60 | : TestModel(parent) 61 | { 62 | setForeignKey("author", new Author(this)); 63 | } 64 | 65 | Author *author() const { return qobject_cast(foreignKey("author")); } 66 | void setAuthor(Author *author) { setForeignKey("author", author); } 67 | 68 | QString title() const { return m_title; } 69 | void setTitle(const QString &title) { m_title = title; } 70 | 71 | private: 72 | QString m_title; 73 | }; 74 | 75 | class BookWithNullAuthor : public TestModel 76 | { 77 | Q_OBJECT 78 | Q_PROPERTY(Author* author READ author WRITE setAuthor) 79 | Q_PROPERTY(QString title READ title WRITE setTitle) 80 | Q_CLASSINFO("author", "null=true on_delete=cascade") 81 | 82 | public: 83 | BookWithNullAuthor(QObject *parent = 0) : TestModel(parent) {} 84 | 85 | Author *author() const { return qobject_cast(foreignKey("author")); } 86 | void setAuthor(Author *author) { setForeignKey("author", author); } 87 | 88 | QString title() const { return m_title; } 89 | void setTitle(const QString &title) { m_title = title; } 90 | private: 91 | QString m_title; 92 | }; 93 | 94 | /** Test QDjangoModel class. 95 | */ 96 | class tst_QDjangoModel : public QObject 97 | { 98 | Q_OBJECT 99 | 100 | private slots: 101 | void initTestCase(); 102 | void init(); 103 | void deleteCascade(); 104 | void foreignKey(); 105 | void foreignKey_null(); 106 | void setForeignKey(); 107 | void filterRelated(); 108 | void filterRelatedReverse(); 109 | void filterRelatedReverse_null(); 110 | void primaryKey(); 111 | void selectRelated(); 112 | void selectRelated_null(); 113 | void toString(); 114 | void cleanup(); 115 | void cleanupTestCase(); 116 | }; 117 | 118 | /** Create database tables before running tests. 119 | */ 120 | void tst_QDjangoModel::initTestCase() 121 | { 122 | QVERIFY(initialiseDatabase()); 123 | QDjango::registerModel(); 124 | QDjango::registerModel(); 125 | QDjango::registerModel(); 126 | } 127 | 128 | /** Load fixtures. 129 | */ 130 | void tst_QDjangoModel::init() 131 | { 132 | QVERIFY(QDjango::createTables()); 133 | 134 | Author author1; 135 | author1.setName("First author"); 136 | QCOMPARE(author1.save(), true); 137 | 138 | Author author2; 139 | author2.setName("Second author"); 140 | QCOMPARE(author2.save(), true); 141 | 142 | Book book; 143 | book.setAuthor(&author1); 144 | book.setTitle("Some book"); 145 | QCOMPARE(book.save(), true); 146 | 147 | Book book2; 148 | book2.setAuthor(&author2); 149 | book2.setTitle("Other book"); 150 | QCOMPARE(book2.save(), true); 151 | 152 | BookWithNullAuthor book3; 153 | book3.setTitle("Book with null author"); 154 | QCOMPARE(book3.save(), true); 155 | 156 | BookWithNullAuthor book4; 157 | book4.setAuthor(&author1); 158 | book4.setTitle("Some book"); 159 | QCOMPARE(book4.save(), true); 160 | } 161 | 162 | void tst_QDjangoModel::deleteCascade() 163 | { 164 | const QDjangoQuerySet authors; 165 | const QDjangoQuerySet books; 166 | QCOMPARE(authors.count(), 2); 167 | QCOMPARE(books.count(), 2); 168 | 169 | QVERIFY(authors.filter(QDjangoWhere("name", QDjangoWhere::Equals, "First author")).remove()); 170 | 171 | QCOMPARE(authors.count(), 1); 172 | QCOMPARE(books.count(), 1); 173 | } 174 | 175 | void tst_QDjangoModel::foreignKey() 176 | { 177 | QTest::ignoreMessage(QtWarningMsg, "QDjangoMetaModel cannot get foreign model for invalid key 'bad'"); 178 | Book book; 179 | QVERIFY(!book.foreignKey("bad")); 180 | QVERIFY(book.foreignKey("author")); 181 | } 182 | 183 | void tst_QDjangoModel::foreignKey_null() 184 | { 185 | QTest::ignoreMessage(QtWarningMsg, "QDjangoMetaModel cannot get foreign model for invalid key 'bad'"); 186 | BookWithNullAuthor book; 187 | QVERIFY(!book.foreignKey("bad")); 188 | QVERIFY(!book.foreignKey("author")); 189 | } 190 | 191 | void tst_QDjangoModel::setForeignKey() 192 | { 193 | QTest::ignoreMessage(QtWarningMsg, "QDjangoMetaModel cannot set foreign model for invalid key 'bad'"); 194 | Book book; 195 | book.setForeignKey("bad", 0); 196 | book.setForeignKey("author", 0); 197 | } 198 | 199 | /** Perform filtering on foreign keys. 200 | */ 201 | void tst_QDjangoModel::filterRelated() 202 | { 203 | QDjangoQuerySet books; 204 | 205 | QDjangoQuerySet qs = books.filter( 206 | QDjangoWhere("author__name", QDjangoWhere::Equals, "First author")); 207 | CHECKWHERE(qs.where(), QLatin1String("T0.\"name\" = ?"), QVariantList() << "First author"); 208 | QCOMPARE(qs.count(), 1); 209 | QCOMPARE(qs.size(), 1); 210 | 211 | Book *book = qs.at(0); 212 | QVERIFY(book != 0); 213 | QCOMPARE(book->title(), QLatin1String("Some book")); 214 | delete book; 215 | } 216 | 217 | void tst_QDjangoModel::filterRelatedReverse() 218 | { 219 | QDjangoQuerySet authors; 220 | QDjangoQuerySet qs = authors.filter( 221 | QDjangoWhere("book__title", QDjangoWhere::Equals, "Some book")); 222 | QVERIFY(!qs.values().isEmpty()); 223 | QCOMPARE(qs.count(), 1); 224 | QCOMPARE(qs.size(), 1); 225 | 226 | Author *author = qs.at(0); 227 | QVERIFY(author != 0); 228 | QCOMPARE(author->name(), QLatin1String("First author")); 229 | delete author; 230 | } 231 | 232 | void tst_QDjangoModel::filterRelatedReverse_null() 233 | { 234 | QDjangoQuerySet authors; 235 | QDjangoQuerySet qs = authors.filter( 236 | QDjangoWhere("bookwithnullauthor__title", QDjangoWhere::Equals, "Some book")); 237 | QVERIFY(!qs.values().isEmpty()); 238 | QCOMPARE(qs.count(), 1); 239 | QCOMPARE(qs.size(), 1); 240 | 241 | Author *author = qs.at(0); 242 | QVERIFY(author != 0); 243 | QCOMPARE(author->name(), QLatin1String("First author")); 244 | delete author; 245 | } 246 | 247 | void tst_QDjangoModel::primaryKey() 248 | { 249 | Author author; 250 | QCOMPARE(author.pk(), QVariant()); 251 | 252 | author.setPk(1); 253 | QCOMPARE(author.pk(), QVariant(1)); 254 | } 255 | 256 | /** Test eager loading of foreign keys. 257 | */ 258 | void tst_QDjangoModel::selectRelated() 259 | { 260 | // without eager loading 261 | QDjangoQuerySet qs; 262 | Book *book = qs.get(QDjangoWhere("title", QDjangoWhere::Equals, "Some book")); 263 | QVERIFY(book != 0); 264 | QCOMPARE(book->title(), QLatin1String("Some book")); 265 | QVERIFY(book->author() != 0); 266 | QCOMPARE(book->author()->name(), QLatin1String("First author")); 267 | delete book; 268 | 269 | // with eager loading 270 | book = qs.selectRelated().get(QDjangoWhere("title", QDjangoWhere::Equals, "Some book")); 271 | QVERIFY(book != 0); 272 | QCOMPARE(book->title(), QLatin1String("Some book")); 273 | QVERIFY(book->author() != 0); 274 | QCOMPARE(book->author()->name(), QLatin1String("First author")); 275 | delete book; 276 | } 277 | 278 | void tst_QDjangoModel::selectRelated_null() 279 | { 280 | // without eager loading 281 | QDjangoQuerySet qs; 282 | BookWithNullAuthor *book = qs.get(QDjangoWhere("title", QDjangoWhere::Equals, "Book with null author")); 283 | QVERIFY(book != 0); 284 | QCOMPARE(book->title(), QLatin1String("Book with null author")); 285 | QVERIFY(!book->author()); 286 | delete book; 287 | 288 | // with eager loading 289 | book = qs.selectRelated().get(QDjangoWhere("title", QDjangoWhere::Equals, "Book with null author")); 290 | QVERIFY(book != 0); 291 | QCOMPARE(book->title(), QLatin1String("Book with null author")); 292 | QVERIFY(!book->author()); 293 | delete book; 294 | } 295 | 296 | void tst_QDjangoModel::toString() 297 | { 298 | QDjangoQuerySet qs; 299 | Book *book = qs.get(QDjangoWhere("title", QDjangoWhere::Equals, "Some book")); 300 | QVERIFY(book != 0); 301 | QCOMPARE(book->toString(), QLatin1String("Book(id=1)")); 302 | delete book; 303 | } 304 | 305 | /** Clear database tables after each test. 306 | */ 307 | void tst_QDjangoModel::cleanup() 308 | { 309 | QVERIFY(QDjango::dropTables()); 310 | } 311 | 312 | /** Drop database tables after running tests. 313 | */ 314 | void tst_QDjangoModel::cleanupTestCase() 315 | { 316 | } 317 | 318 | QTEST_MAIN(tst_QDjangoModel) 319 | #include "tst_qdjangomodel.moc" 320 | -------------------------------------------------------------------------------- /tests/db/qdjangoqueryset/qdjangoqueryset.pro: -------------------------------------------------------------------------------- 1 | include(../db.pri) 2 | 3 | TARGET = tst_qdjangoqueryset 4 | SOURCES += tst_qdjangoqueryset.cpp 5 | -------------------------------------------------------------------------------- /tests/db/qdjangoqueryset/tst_qdjangoqueryset.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include "QDjango.h" 19 | #include "QDjangoQuerySet.h" 20 | #include "QDjangoWhere.h" 21 | 22 | #include "auth-models.h" 23 | #include "util.h" 24 | 25 | class Object : public QObject 26 | { 27 | Q_OBJECT 28 | Q_PROPERTY(QString foo READ foo WRITE setFoo) 29 | Q_PROPERTY(int bar READ bar WRITE setBar) 30 | 31 | Q_CLASSINFO("__meta__", "db_table=foo_table") 32 | Q_CLASSINFO("foo", "max_length=255") 33 | Q_CLASSINFO("bar", "db_column=bar_column") 34 | 35 | public: 36 | QString foo() const { return m_foo; }; 37 | void setFoo(const QString &foo) { m_foo = foo; }; 38 | 39 | int bar() const { return m_bar; }; 40 | void setBar(int bar) { m_bar = bar; }; 41 | 42 | private: 43 | QString m_foo; 44 | int m_bar; 45 | }; 46 | 47 | /** Test QDjangoQuerySetPrivate class. 48 | */ 49 | class tst_QDjangoQuerySetPrivate : public QObject 50 | { 51 | Q_OBJECT 52 | 53 | private slots: 54 | void initTestCase(); 55 | void aggregateQuery(); 56 | void countQuery(); 57 | void deleteQuery(); 58 | void insertQuery(); 59 | void selectQuery(); 60 | void updateQuery(); 61 | void cleanupTestCase(); 62 | 63 | private: 64 | QDjangoMetaModel metaModel; 65 | }; 66 | 67 | void tst_QDjangoQuerySetPrivate::initTestCase() 68 | { 69 | QVERIFY(initialiseDatabase()); 70 | 71 | metaModel = QDjango::registerModel(); 72 | QCOMPARE(metaModel.createTable(), true); 73 | } 74 | 75 | void tst_QDjangoQuerySetPrivate::countQuery() 76 | { 77 | QDjangoQuerySetPrivate qs("Object"); 78 | QDjangoQuery query = qs.aggregateQuery(QDjangoWhere::COUNT, "*"); 79 | QVERIFY(query.exec()); 80 | QVERIFY(query.next()); 81 | QCOMPARE(normalizeSql(QDjango::database(), query.lastQuery()), QLatin1String("SELECT COUNT(*) FROM \"foo_table\"")); 82 | int count = query.value(0).isValid() ? query.value(0).toInt() : -1; 83 | //We've added 10 items in aggregateQuery() 84 | QCOMPARE(count, 10); 85 | } 86 | 87 | void tst_QDjangoQuerySetPrivate::deleteQuery() 88 | { 89 | QDjangoQuerySetPrivate qs("Object"); 90 | qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, 1)); 91 | QDjangoQuery query = qs.deleteQuery(); 92 | 93 | QCOMPARE(normalizeSql(QDjango::database(), query.lastQuery()), QLatin1String("DELETE FROM \"foo_table\" WHERE \"foo_table\".\"id\" = ?")); 94 | QCOMPARE(query.boundValues().size(), 1); 95 | QCOMPARE(query.boundValue(0), QVariant(1)); 96 | } 97 | 98 | void tst_QDjangoQuerySetPrivate::insertQuery() 99 | { 100 | QVariantMap data; 101 | data.insert("foo", "abc"); 102 | 103 | QDjangoQuerySetPrivate qs("Object"); 104 | QDjangoQuery query = qs.insertQuery(data); 105 | 106 | QCOMPARE(normalizeSql(QDjango::database(), query.lastQuery()), QLatin1String("INSERT INTO \"foo_table\" (\"foo\") VALUES(?)")); 107 | QCOMPARE(query.boundValues().size(), 1); 108 | QCOMPARE(query.boundValue(0), QVariant("abc")); 109 | } 110 | 111 | void tst_QDjangoQuerySetPrivate::aggregateQuery() 112 | { 113 | for (int i=0;i<10;i++){ 114 | QVariantMap data; 115 | data.insert("foo", "abc"); 116 | data.insert("bar",i+1); 117 | QDjangoQuerySetPrivate qs("Object"); 118 | QDjangoQuery query = qs.insertQuery(data); 119 | QVERIFY(query.exec()); 120 | } 121 | QDjangoQuerySetPrivate qs("Object"); 122 | QDjangoQuery query = qs.aggregateQuery(QDjangoWhere::SUM, "bar_column"); 123 | QVERIFY(query.exec()); 124 | QVERIFY(query.next()); 125 | QCOMPARE(normalizeSql(QDjango::database(), query.lastQuery()), QLatin1String("SELECT SUM(bar_column) FROM \"foo_table\"")); 126 | QCOMPARE(query.value(0).toInt(),55); 127 | qs.addFilter(QDjangoWhere("bar",QDjangoWhere::GreaterThan,5)); 128 | query = qs.aggregateQuery(QDjangoWhere::SUM, "bar_column"); 129 | QVERIFY(query.exec()); 130 | QVERIFY(query.next()); 131 | QCOMPARE(query.value(0).toInt(),40); 132 | } 133 | 134 | void tst_QDjangoQuerySetPrivate::selectQuery() 135 | { 136 | QVariantMap data; 137 | data.insert("foo", "abc"); 138 | 139 | { 140 | QDjangoQuerySetPrivate qs("Object"); 141 | qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, 1)); 142 | QDjangoQuery query = qs.selectQuery(); 143 | 144 | QCOMPARE(normalizeSql(QDjango::database(), query.lastQuery()), QLatin1String("SELECT \"foo_table\".\"id\", \"foo_table\".\"foo\", \"foo_table\".\"bar_column\" FROM \"foo_table\" WHERE \"foo_table\".\"id\" = ?")); 145 | QCOMPARE(query.boundValues().size(), 1); 146 | QCOMPARE(query.boundValue(0), QVariant(1)); 147 | } 148 | 149 | { 150 | QDjangoQuerySetPrivate qs("Object"); 151 | qs.addFilter(QDjangoWhere("bar", QDjangoWhere::Equals, 3)); 152 | QDjangoQuery query = qs.selectQuery(); 153 | 154 | QCOMPARE(normalizeSql(QDjango::database(), query.lastQuery()), QLatin1String("SELECT \"foo_table\".\"id\", \"foo_table\".\"foo\", \"foo_table\".\"bar_column\" FROM \"foo_table\" WHERE \"foo_table\".\"bar_column\" = ?")); 155 | QCOMPARE(query.boundValues().size(), 1); 156 | QCOMPARE(query.boundValue(0), QVariant(3)); 157 | } 158 | } 159 | 160 | void tst_QDjangoQuerySetPrivate::updateQuery() 161 | { 162 | QVariantMap data; 163 | data.insert("foo", "abc"); 164 | 165 | { 166 | QDjangoQuerySetPrivate qs("Object"); 167 | qs.addFilter(QDjangoWhere("pk", QDjangoWhere::Equals, 1)); 168 | QDjangoQuery query = qs.updateQuery(data); 169 | 170 | QCOMPARE(normalizeSql(QDjango::database(), query.lastQuery()), QLatin1String("UPDATE \"foo_table\" SET \"foo\" = ? WHERE \"foo_table\".\"id\" = ?")); 171 | QCOMPARE(query.boundValues().size(), 2); 172 | QCOMPARE(query.boundValue(0), QVariant("abc")); 173 | QCOMPARE(query.boundValue(1), QVariant(1)); 174 | } 175 | 176 | { 177 | QDjangoQuerySetPrivate qs("Object"); 178 | qs.addFilter(QDjangoWhere("bar", QDjangoWhere::Equals, 3)); 179 | QDjangoQuery query = qs.updateQuery(data); 180 | 181 | QCOMPARE(normalizeSql(QDjango::database(), query.lastQuery()), QLatin1String("UPDATE \"foo_table\" SET \"foo\" = ? WHERE \"foo_table\".\"bar_column\" = ?")); 182 | QCOMPARE(query.boundValues().size(), 2); 183 | QCOMPARE(query.boundValue(0), QVariant("abc")); 184 | QCOMPARE(query.boundValue(1), QVariant(3)); 185 | } 186 | } 187 | 188 | void tst_QDjangoQuerySetPrivate::cleanupTestCase() 189 | { 190 | metaModel.dropTable(); 191 | } 192 | 193 | QTEST_MAIN(tst_QDjangoQuerySetPrivate) 194 | #include "tst_qdjangoqueryset.moc" 195 | -------------------------------------------------------------------------------- /tests/db/qdjangowhere/qdjangowhere.pro: -------------------------------------------------------------------------------- 1 | include(../db.pri) 2 | 3 | TARGET = tst_qdjangowhere 4 | SOURCES += tst_qdjangowhere.cpp 5 | -------------------------------------------------------------------------------- /tests/db/shares/shares.pro: -------------------------------------------------------------------------------- 1 | include(../db.pri) 2 | 3 | TARGET = tst_shares 4 | SOURCES += tst_shares.cpp 5 | -------------------------------------------------------------------------------- /tests/db/shares/tst_shares.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include "QDjangoQuerySet.h" 19 | 20 | #include "util.h" 21 | 22 | /** Tests for the File class. 23 | */ 24 | class tst_Shares : public QObject 25 | { 26 | Q_OBJECT 27 | 28 | private slots: 29 | void initTestCase(); 30 | void testFile(); 31 | void cleanup(); 32 | void cleanupTestCase(); 33 | }; 34 | 35 | class File : public QDjangoModel 36 | { 37 | Q_OBJECT 38 | Q_PROPERTY(QDateTime date READ date WRITE setDate) 39 | Q_PROPERTY(QByteArray hash READ hash WRITE setHash) 40 | Q_PROPERTY(QString path READ path WRITE setPath) 41 | Q_PROPERTY(qint64 size READ size WRITE setSize) 42 | 43 | Q_CLASSINFO("path", "max_length=255 primary_key=true") 44 | Q_CLASSINFO("hash", "max_length=32") 45 | 46 | public: 47 | File(QObject *parent = 0); 48 | 49 | QDateTime date() const; 50 | void setDate(const QDateTime &date); 51 | 52 | QByteArray hash() const; 53 | void setHash(const QByteArray &hash); 54 | 55 | QString path() const; 56 | void setPath(const QString &path); 57 | 58 | qint64 size() const; 59 | void setSize(qint64 size); 60 | 61 | private: 62 | QDateTime m_date; 63 | QByteArray m_hash; 64 | QString m_path; 65 | qint64 m_size; 66 | }; 67 | 68 | File::File(QObject *parent) 69 | : QDjangoModel(parent), m_size(0) 70 | { 71 | } 72 | 73 | QDateTime File::date() const 74 | { 75 | return m_date; 76 | } 77 | 78 | void File::setDate(const QDateTime &date) 79 | { 80 | m_date = date; 81 | } 82 | 83 | QByteArray File::hash() const 84 | { 85 | return m_hash; 86 | } 87 | 88 | void File::setHash(const QByteArray &hash) 89 | { 90 | m_hash = hash; 91 | } 92 | 93 | QString File::path() const 94 | { 95 | return m_path; 96 | } 97 | 98 | void File::setPath(const QString &path) 99 | { 100 | m_path = path; 101 | } 102 | 103 | qint64 File::size() const 104 | { 105 | return m_size; 106 | } 107 | 108 | void File::setSize(qint64 size) 109 | { 110 | m_size = size; 111 | } 112 | 113 | /** Create database table before running tests. 114 | */ 115 | void tst_Shares::initTestCase() 116 | { 117 | QVERIFY(initialiseDatabase()); 118 | QDjango::registerModel(); 119 | QVERIFY(QDjango::createTables()); 120 | } 121 | 122 | void tst_Shares::testFile() 123 | { 124 | // create a file 125 | File file; 126 | file.setDate(QDateTime(QDate(2010, 6, 1), QTime(10, 5, 14))); 127 | file.setHash(QByteArray("\0\1\2\3\4", 5)); 128 | file.setPath("foo/bar.txt"); 129 | file.setSize(1234); 130 | QCOMPARE(file.save(), true); 131 | 132 | File *other = QDjangoQuerySet().get(QDjangoWhere("path", QDjangoWhere::Equals, "foo/bar.txt")); 133 | QVERIFY(other != 0); 134 | QCOMPARE(other->date(), QDateTime(QDate(2010, 6, 1), QTime(10, 5, 14))); 135 | QCOMPARE(other->hash(), QByteArray("\0\1\2\3\4", 5)); 136 | QCOMPARE(other->path(), QLatin1String("foo/bar.txt")); 137 | QCOMPARE(other->size(), qint64(1234)); 138 | delete other; 139 | 140 | // update the file 141 | file.setSize(5678); 142 | QCOMPARE(file.save(), true); 143 | } 144 | 145 | /** Clear database table after each test. 146 | */ 147 | void tst_Shares::cleanup() 148 | { 149 | QCOMPARE(QDjangoQuerySet().remove(), true); 150 | } 151 | 152 | /** Drop database table after running tests. 153 | */ 154 | void tst_Shares::cleanupTestCase() 155 | { 156 | QVERIFY(QDjango::dropTables()); 157 | } 158 | 159 | QTEST_MAIN(tst_Shares) 160 | #include "tst_shares.moc" 161 | -------------------------------------------------------------------------------- /tests/db/util.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | 20 | #include "QDjango.h" 21 | 22 | #include "util.h" 23 | 24 | bool initialiseDatabase() 25 | { 26 | char *p; 27 | 28 | // enable SQL debugging 29 | if ((p = getenv("QDJANGO_DB_DEBUG")) != 0) 30 | QDjango::setDebugEnabled(true); 31 | 32 | // open database 33 | QString databaseDriver = "QSQLITE"; 34 | if ((p = getenv("QDJANGO_DB_DRIVER")) != 0) 35 | databaseDriver = QString::fromLocal8Bit(p); 36 | QSqlDatabase db = QSqlDatabase::addDatabase(databaseDriver); 37 | 38 | if ((p = getenv("QDJANGO_DB_NAME")) != 0) 39 | db.setDatabaseName(QString::fromLocal8Bit(p)); 40 | else if (databaseDriver == "QSQLITE") 41 | db.setDatabaseName(":memory:"); 42 | 43 | if ((p = getenv("QDJANGO_DB_USER")) != 0) 44 | db.setUserName(QString::fromLocal8Bit(p)); 45 | 46 | if ((p = getenv("QDJANGO_DB_PASSWORD")) != 0) 47 | db.setPassword(QString::fromLocal8Bit(p)); 48 | 49 | if ((p = getenv("QDJANGO_DB_HOST")) != 0) 50 | db.setHostName(QString::fromLocal8Bit(p)); 51 | 52 | if (db.open()) { 53 | QDjango::setDatabase(db); 54 | return true; 55 | } else { 56 | return false; 57 | } 58 | } 59 | 60 | QString normalizeSql(const QSqlDatabase &db, const QString &sql) 61 | { 62 | QDjangoDatabase::DatabaseType databaseType = QDjangoDatabase::databaseType(db); 63 | QString modSql(sql); 64 | if (databaseType == QDjangoDatabase::MySqlServer) 65 | modSql.replace("`", "\""); 66 | else if (databaseType == QDjangoDatabase::SQLite) 67 | modSql.replace("? ESCAPE '\\'", "?"); 68 | return modSql; 69 | } 70 | -------------------------------------------------------------------------------- /tests/db/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | 20 | #include "QDjango.h" 21 | #include "QDjangoModel.h" 22 | 23 | bool initialiseDatabase(); 24 | QString normalizeSql(const QSqlDatabase &db, const QString &sql); 25 | 26 | #define CHECKWHERE(_where, s, v) { \ 27 | QSqlDatabase _sql_db(QDjango::database()); \ 28 | QDjangoQuery _sql_query(_sql_db); \ 29 | QString _sql_stmt = _where.sql(_sql_db); \ 30 | if (!_sql_stmt.isEmpty()) _sql_query.prepare(_sql_stmt); \ 31 | _where.bindValues(_sql_query); \ 32 | const QVariantList _sql_values = v; \ 33 | QCOMPARE(normalizeSql(_sql_db, _sql_query.lastQuery()), s); \ 34 | QCOMPARE(_sql_query.boundValues().size(), _sql_values.size()); \ 35 | for(int _i = 0; _i < _sql_values.size(); ++_i) QCOMPARE(_sql_query.boundValue(_i), _sql_values[_i]); \ 36 | } 37 | 38 | -------------------------------------------------------------------------------- /tests/http/http.pri: -------------------------------------------------------------------------------- 1 | include(../tests.pri) 2 | 3 | QT += network 4 | 5 | LIBS += -L../../../src/http $$QDJANGO_HTTP_LIBS 6 | -------------------------------------------------------------------------------- /tests/http/http.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS = \ 3 | qdjangofastcgiserver \ 4 | qdjangohttpcontroller \ 5 | qdjangohttprequest \ 6 | qdjangohttpresponse \ 7 | qdjangohttpserver \ 8 | qdjangourlresolver 9 | -------------------------------------------------------------------------------- /tests/http/qdjangofastcgiserver/qdjangofastcgiserver.pro: -------------------------------------------------------------------------------- 1 | include(../http.pri) 2 | 3 | TARGET = tst_qdjangofastcgiserver 4 | SOURCES += tst_qdjangofastcgiserver.cpp 5 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpcontroller/qdjangohttpcontroller.pro: -------------------------------------------------------------------------------- 1 | include(../http.pri) 2 | 3 | TARGET = tst_qdjangohttpcontroller 4 | SOURCES += tst_qdjangohttpcontroller.cpp 5 | RESOURCES += tst_qdjangohttpcontroller.qrc 6 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpcontroller/test.bin: -------------------------------------------------------------------------------- 1 | DATA! 2 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpcontroller/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: 'red'; 3 | } 4 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpcontroller/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello!

4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpcontroller/test.js: -------------------------------------------------------------------------------- 1 | console.log("foo!"); 2 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpcontroller/tst_qdjangohttpcontroller.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #include "QDjangoHttpController.h" 22 | #include "QDjangoHttpRequest.h" 23 | #include "QDjangoHttpRequest_p.h" 24 | #include "QDjangoHttpResponse.h" 25 | 26 | /** Test QDjangoHttpController class. 27 | */ 28 | class tst_QDjangoHttpController : public QObject 29 | { 30 | Q_OBJECT 31 | 32 | private slots: 33 | void testBasicAuth(); 34 | void testDateTime(); 35 | void testServeAuthorizationRequired(); 36 | void testServeBadRequest(); 37 | void testServeInternalServerError(); 38 | void testServeNotFound(); 39 | void testServeRedirect(); 40 | void testServeStatic(); 41 | void testServeStaticHead(); 42 | void testServeStaticIfModifiedSince(); 43 | }; 44 | 45 | void tst_QDjangoHttpController::testBasicAuth() 46 | { 47 | QDjangoHttpRequest request; 48 | 49 | QString username, password; 50 | QCOMPARE(QDjangoHttpController::getBasicAuth(request, username, password), false); 51 | QCOMPARE(username, QString()); 52 | QCOMPARE(password, QString()); 53 | 54 | request.d->meta.insert("HTTP_AUTHORIZATION", "Basic bad"); 55 | QCOMPARE(QDjangoHttpController::getBasicAuth(request, username, password), false); 56 | QCOMPARE(username, QString()); 57 | QCOMPARE(password, QString()); 58 | 59 | request.d->meta.insert("HTTP_AUTHORIZATION", "Basic Zm9vOmJhcg=="); 60 | QCOMPARE(QDjangoHttpController::getBasicAuth(request, username, password), true); 61 | QCOMPARE(username, QString("foo")); 62 | QCOMPARE(password, QString("bar")); 63 | } 64 | 65 | void tst_QDjangoHttpController::testDateTime() 66 | { 67 | const QDateTime dt(QDate(2014, 7, 14), QTime(11, 22, 33), Qt::UTC); 68 | QCOMPARE(QDjangoHttpController::httpDateTime(dt), QString("Mon, 14 Jul 2014 11:22:33 GMT")); 69 | QCOMPARE(QDjangoHttpController::httpDateTime("Mon, 14 Jul 2014 11:22:33 GMT"), dt); 70 | } 71 | 72 | void tst_QDjangoHttpController::testServeAuthorizationRequired() 73 | { 74 | QDjangoHttpRequest request; 75 | QDjangoHttpResponse *response = QDjangoHttpController::serveAuthorizationRequired(request); 76 | QCOMPARE(response->statusCode(), 401); 77 | QCOMPARE(response->header("www-authenticate"), QString("Basic realm=\"Secure Area\"")); 78 | delete response; 79 | } 80 | 81 | void tst_QDjangoHttpController::testServeBadRequest() 82 | { 83 | QDjangoHttpRequest request; 84 | QDjangoHttpResponse *response = QDjangoHttpController::serveBadRequest(request); 85 | QCOMPARE(response->statusCode(), 400); 86 | delete response; 87 | } 88 | 89 | void tst_QDjangoHttpController::testServeInternalServerError() 90 | { 91 | QDjangoHttpRequest request; 92 | QDjangoHttpResponse *response = QDjangoHttpController::serveInternalServerError(request); 93 | QCOMPARE(response->statusCode(), 500); 94 | delete response; 95 | } 96 | 97 | void tst_QDjangoHttpController::testServeNotFound() 98 | { 99 | QDjangoHttpRequest request; 100 | QDjangoHttpResponse *response = QDjangoHttpController::serveNotFound(request); 101 | QCOMPARE(response->statusCode(), 404); 102 | delete response; 103 | } 104 | 105 | void tst_QDjangoHttpController::testServeRedirect() 106 | { 107 | QDjangoHttpRequest request; 108 | 109 | QDjangoHttpResponse *response = QDjangoHttpController::serveRedirect(request, QUrl("/bye"), false); 110 | QCOMPARE(response->statusCode(), 302); 111 | QCOMPARE(response->header("location"), QString("/bye")); 112 | delete response; 113 | 114 | response = QDjangoHttpController::serveRedirect(request, QUrl("/bye"), true); 115 | QCOMPARE(response->statusCode(), 301); 116 | QCOMPARE(response->header("location"), QString("/bye")); 117 | delete response; 118 | } 119 | 120 | void tst_QDjangoHttpController::testServeStatic() 121 | { 122 | QDjangoHttpRequest request; 123 | QDjangoHttpResponse *response; 124 | 125 | request.d->method = "GET"; 126 | response = QDjangoHttpController::serveStatic(request, ":/not-found"); 127 | QCOMPARE(response->statusCode(), 404); 128 | QCOMPARE(response->header("content-type"), QString("text/html; charset=utf-8")); 129 | QCOMPARE(response->header("content-length"), QString("107")); 130 | QCOMPARE(response->header("expires"), QString()); 131 | QVERIFY(response->header("last-modified").isEmpty()); 132 | delete response; 133 | 134 | response = QDjangoHttpController::serveStatic(request, ":/test.bin"); 135 | QCOMPARE(response->statusCode(), 200); 136 | QCOMPARE(response->header("content-type"), QString("application/octet-stream")); 137 | QCOMPARE(response->header("content-length"), QString("6")); 138 | QCOMPARE(response->header("expires"), QString()); 139 | QVERIFY(!response->header("last-modified").isEmpty()); 140 | delete response; 141 | 142 | response = QDjangoHttpController::serveStatic(request, ":/test.css"); 143 | QCOMPARE(response->statusCode(), 200); 144 | QCOMPARE(response->header("content-type"), QString("text/css")); 145 | QCOMPARE(response->header("content-length"), QString("27")); 146 | QCOMPARE(response->header("expires"), QString()); 147 | QVERIFY(!response->header("last-modified").isEmpty()); 148 | QCOMPARE(response->body().size(), 27); 149 | delete response; 150 | 151 | response = QDjangoHttpController::serveStatic(request, ":/test.js"); 152 | QCOMPARE(response->statusCode(), 200); 153 | QCOMPARE(response->header("content-type"), QString("application/javascript")); 154 | QCOMPARE(response->header("content-length"), QString("21")); 155 | QCOMPARE(response->header("expires"), QString()); 156 | QVERIFY(!response->header("last-modified").isEmpty()); 157 | QCOMPARE(response->body().size(), 21); 158 | delete response; 159 | 160 | response = QDjangoHttpController::serveStatic(request, ":/test.html"); 161 | QCOMPARE(response->statusCode(), 200); 162 | QCOMPARE(response->header("content-type"), QString("text/html")); 163 | QCOMPARE(response->header("content-length"), QString("48")); 164 | QCOMPARE(response->header("expires"), QString()); 165 | QVERIFY(!response->header("last-modified").isEmpty()); 166 | QCOMPARE(response->body().size(), 48); 167 | delete response; 168 | 169 | // expires 170 | const QDateTime expires(QDate(2014, 7, 14), QTime(11, 22, 33), Qt::UTC); 171 | response = QDjangoHttpController::serveStatic(request, ":/test.html", expires); 172 | QCOMPARE(response->statusCode(), 200); 173 | QCOMPARE(response->header("content-type"), QString("text/html")); 174 | QCOMPARE(response->header("content-length"), QString("48")); 175 | QCOMPARE(response->header("expires"), QString("Mon, 14 Jul 2014 11:22:33 GMT")); 176 | QVERIFY(!response->header("last-modified").isEmpty()); 177 | QCOMPARE(response->body().size(), 48); 178 | delete response; 179 | } 180 | 181 | void tst_QDjangoHttpController::testServeStaticHead() 182 | { 183 | QDjangoHttpRequest request; 184 | request.d->method = "HEAD"; 185 | 186 | QDjangoHttpResponse *response = QDjangoHttpController::serveStatic(request, ":/test.html"); 187 | QCOMPARE(response->statusCode(), 200); 188 | QCOMPARE(response->header("content-type"), QString("text/html")); 189 | QCOMPARE(response->header("content-length"), QString("48")); 190 | QCOMPARE(response->header("expires"), QString()); 191 | QVERIFY(!response->header("last-modified").isEmpty()); 192 | QCOMPARE(response->body().size(), 0); 193 | delete response; 194 | } 195 | 196 | void tst_QDjangoHttpController::testServeStaticIfModifiedSince() 197 | { 198 | QDjangoHttpRequest request; 199 | request.d->method = "GET"; 200 | request.d->meta.insert("HTTP_IF_MODIFIED_SINCE", "Tue, 14 Jul 2054 11:22:33 GMT"); 201 | 202 | QDjangoHttpResponse *response = QDjangoHttpController::serveStatic(request, ":/test.html"); 203 | QCOMPARE(response->statusCode(), 304); 204 | QCOMPARE(response->header("content-type"), QString()); 205 | QCOMPARE(response->header("content-length"), QString("0")); 206 | QCOMPARE(response->header("expires"), QString()); 207 | QVERIFY(!response->header("last-modified").isEmpty()); 208 | QCOMPARE(response->body().size(), 0); 209 | delete response; 210 | } 211 | 212 | QTEST_MAIN(tst_QDjangoHttpController) 213 | #include "tst_qdjangohttpcontroller.moc" 214 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpcontroller/tst_qdjangohttpcontroller.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test.bin 5 | test.css 6 | test.js 7 | test.html 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/http/qdjangohttprequest/qdjangohttprequest.pro: -------------------------------------------------------------------------------- 1 | include(../http.pri) 2 | 3 | TARGET = tst_qdjangohttprequest 4 | SOURCES += tst_qdjangohttprequest.cpp 5 | -------------------------------------------------------------------------------- /tests/http/qdjangohttprequest/tst_qdjangohttprequest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | 20 | #include "QDjangoHttpRequest.h" 21 | #include "QDjangoHttpRequest_p.h" 22 | 23 | /** Test QDjangoHttpServer class. 24 | */ 25 | class tst_QDjangoHttpRequest : public QObject 26 | { 27 | Q_OBJECT 28 | 29 | private slots: 30 | void testBody(); 31 | void testGet(); 32 | void testPost(); 33 | }; 34 | 35 | void tst_QDjangoHttpRequest::testBody() 36 | { 37 | QDjangoHttpRequest request; 38 | 39 | QCOMPARE(request.body(), QByteArray()); 40 | 41 | request.d->buffer = QByteArray("foo=bar"); 42 | QCOMPARE(request.body(), QByteArray("foo=bar")); 43 | } 44 | 45 | void tst_QDjangoHttpRequest::testGet() 46 | { 47 | QDjangoHttpRequest request; 48 | 49 | // plain 50 | request.d->meta.insert("QUERY_STRING", "foo=bar&baz=qux"); 51 | QCOMPARE(request.get(QLatin1String("foo")), QLatin1String("bar")); 52 | QCOMPARE(request.get(QLatin1String("baz")), QLatin1String("qux")); 53 | 54 | // space encoded as plus 55 | request.d->meta.insert("QUERY_STRING", "foo=bar+more&baz=qux"); 56 | QCOMPARE(request.get(QLatin1String("foo")), QLatin1String("bar more")); 57 | QCOMPARE(request.get(QLatin1String("baz")), QLatin1String("qux")); 58 | 59 | // plus encoded as %2B 60 | request.d->meta.insert("QUERY_STRING", "foo=bar%2Bmore&baz=qux"); 61 | QCOMPARE(request.get(QLatin1String("foo")), QLatin1String("bar+more")); 62 | QCOMPARE(request.get(QLatin1String("baz")), QLatin1String("qux")); 63 | 64 | // plus encoded as %2b 65 | request.d->meta.insert("QUERY_STRING", "foo=bar%2bmore&baz=qux"); 66 | QCOMPARE(request.get(QLatin1String("foo")), QLatin1String("bar+more")); 67 | QCOMPARE(request.get(QLatin1String("baz")), QLatin1String("qux")); 68 | 69 | // at encoded as %40 70 | request.d->meta.insert("QUERY_STRING", "foo=bar%40example.com&baz=qux"); 71 | QCOMPARE(request.get(QLatin1String("foo")), QLatin1String("bar@example.com")); 72 | QCOMPARE(request.get(QLatin1String("baz")), QLatin1String("qux")); 73 | } 74 | 75 | void tst_QDjangoHttpRequest::testPost() 76 | { 77 | QDjangoHttpRequest request; 78 | 79 | // plain 80 | request.d->buffer = QByteArray("foo=bar&baz=qux"); 81 | QCOMPARE(request.post(QLatin1String("foo")), QLatin1String("bar")); 82 | QCOMPARE(request.post(QLatin1String("baz")), QLatin1String("qux")); 83 | 84 | // space encoded as plus 85 | request.d->buffer = QByteArray("foo=bar+more&baz=qux"); 86 | QCOMPARE(request.post(QLatin1String("foo")), QLatin1String("bar more")); 87 | QCOMPARE(request.post(QLatin1String("baz")), QLatin1String("qux")); 88 | 89 | // plus encoded as %2B 90 | request.d->buffer = QByteArray("foo=bar%2Bmore&baz=qux"); 91 | QCOMPARE(request.post(QLatin1String("foo")), QLatin1String("bar+more")); 92 | QCOMPARE(request.post(QLatin1String("baz")), QLatin1String("qux")); 93 | 94 | // plus encoded as %2b 95 | request.d->buffer = QByteArray("foo=bar%2bmore&baz=qux"); 96 | QCOMPARE(request.post(QLatin1String("foo")), QLatin1String("bar+more")); 97 | QCOMPARE(request.post(QLatin1String("baz")), QLatin1String("qux")); 98 | 99 | // at encoded as %40 100 | request.d->buffer = QByteArray("foo=bar%40example.com&baz=qux"); 101 | QCOMPARE(request.post(QLatin1String("foo")), QLatin1String("bar@example.com")); 102 | QCOMPARE(request.post(QLatin1String("baz")), QLatin1String("qux")); 103 | } 104 | 105 | QTEST_MAIN(tst_QDjangoHttpRequest) 106 | #include "tst_qdjangohttprequest.moc" 107 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpresponse/qdjangohttpresponse.pro: -------------------------------------------------------------------------------- 1 | include(../http.pri) 2 | 3 | TARGET = tst_qdjangohttpresponse 4 | SOURCES += tst_qdjangohttpresponse.cpp 5 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpresponse/tst_qdjangohttpresponse.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | 20 | #include "QDjangoHttpResponse.h" 21 | 22 | /** Test QDjangoHttpServer class. 23 | */ 24 | class tst_QDjangoHttpResponse : public QObject 25 | { 26 | Q_OBJECT 27 | 28 | private slots: 29 | void testBody(); 30 | void testHeader(); 31 | void testStatusCode_data(); 32 | void testStatusCode(); 33 | }; 34 | 35 | void tst_QDjangoHttpResponse::testBody() 36 | { 37 | QDjangoHttpResponse response; 38 | QCOMPARE(response.body(), QByteArray()); 39 | 40 | response.setBody("foo=bar"); 41 | QCOMPARE(response.body(), QByteArray("foo=bar")); 42 | } 43 | 44 | void tst_QDjangoHttpResponse::testHeader() 45 | { 46 | QDjangoHttpResponse response; 47 | QCOMPARE(response.header("Content-Type"), QString()); 48 | 49 | response.setHeader("Content-Type", "application/json"); 50 | QCOMPARE(response.header("Content-Type"), QString("application/json")); 51 | QCOMPARE(response.header("content-type"), QString("application/json")); 52 | } 53 | 54 | void tst_QDjangoHttpResponse::testStatusCode_data() 55 | { 56 | QTest::addColumn("statusCode"); 57 | QTest::addColumn("reasonPhrase"); 58 | 59 | QTest::newRow("200") << int(200) << QString("OK"); 60 | QTest::newRow("301") << int(301) << QString("Moved Permanently"); 61 | QTest::newRow("302") << int(302) << QString("Found"); 62 | QTest::newRow("304") << int(304) << QString("Not Modified"); 63 | QTest::newRow("400") << int(400) << QString("Bad Request"); 64 | QTest::newRow("401") << int(401) << QString("Authorization Required"); 65 | QTest::newRow("403") << int(403) << QString("Forbidden"); 66 | QTest::newRow("403") << int(404) << QString("Not Found"); 67 | QTest::newRow("405") << int(405) << QString("Method Not Allowed"); 68 | QTest::newRow("500") << int(500) << QString("Internal Server Error"); 69 | QTest::newRow("501") << int(501) << QString(); 70 | } 71 | 72 | void tst_QDjangoHttpResponse::testStatusCode() 73 | { 74 | QFETCH(int, statusCode); 75 | QFETCH(QString, reasonPhrase); 76 | 77 | QDjangoHttpResponse response; 78 | response.setStatusCode(statusCode); 79 | QCOMPARE(response.statusCode(), statusCode); 80 | QCOMPARE(response.reasonPhrase(), reasonPhrase); 81 | } 82 | 83 | QTEST_MAIN(tst_QDjangoHttpResponse) 84 | #include "tst_qdjangohttpresponse.moc" 85 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpserver/qdjangohttpserver.pro: -------------------------------------------------------------------------------- 1 | include(../http.pri) 2 | 3 | TARGET = tst_qdjangohttpserver 4 | SOURCES += tst_qdjangohttpserver.cpp 5 | -------------------------------------------------------------------------------- /tests/http/qdjangohttpserver/tst_qdjangohttpserver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "QDjangoHttpController.h" 25 | #include "QDjangoHttpRequest.h" 26 | #include "QDjangoHttpResponse.h" 27 | #include "QDjangoHttpServer.h" 28 | #include "QDjangoUrlResolver.h" 29 | 30 | /** Test QDjangoHttpServer class. 31 | */ 32 | class tst_QDjangoHttpServer : public QObject 33 | { 34 | Q_OBJECT 35 | 36 | private slots: 37 | void cleanupTestCase(); 38 | void initTestCase(); 39 | void testCloseConnection(); 40 | void testGet_data(); 41 | void testGet(); 42 | void testPost_data(); 43 | void testPost(); 44 | 45 | QDjangoHttpResponse* _q_index(const QDjangoHttpRequest &request); 46 | QDjangoHttpResponse* _q_error(const QDjangoHttpRequest &request); 47 | 48 | private: 49 | QDjangoHttpServer *httpServer; 50 | }; 51 | 52 | 53 | void tst_QDjangoHttpServer::cleanupTestCase() 54 | { 55 | httpServer->close(); 56 | delete httpServer; 57 | } 58 | 59 | void tst_QDjangoHttpServer::initTestCase() 60 | { 61 | httpServer = new QDjangoHttpServer; 62 | httpServer->urls()->set(QRegExp(QLatin1String("^$")), this, "_q_index"); 63 | httpServer->urls()->set(QRegExp(QLatin1String("^internal-server-error$")), this, "_q_error"); 64 | QCOMPARE(httpServer->serverAddress(), QHostAddress(QHostAddress::Null)); 65 | QCOMPARE(httpServer->serverPort(), quint16(0)); 66 | QCOMPARE(httpServer->listen(QHostAddress::LocalHost, 8123), true); 67 | QCOMPARE(httpServer->serverAddress(), QHostAddress(QHostAddress::LocalHost)); 68 | QCOMPARE(httpServer->serverPort(), quint16(8123)); 69 | } 70 | 71 | void tst_QDjangoHttpServer::testCloseConnection() 72 | { 73 | QNetworkAccessManager network; 74 | 75 | QNetworkRequest req(QUrl("http://127.0.0.1:8123/")); 76 | req.setRawHeader("Connection", "close"); 77 | 78 | QNetworkReply *reply = network.get(req); 79 | 80 | QEventLoop loop; 81 | QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); 82 | loop.exec(); 83 | 84 | QVERIFY(reply); 85 | QCOMPARE(reply->error(), QNetworkReply::NoError); 86 | //QCOMPARE(reply->readAll(), body); 87 | delete reply; 88 | 89 | } 90 | 91 | void tst_QDjangoHttpServer::testGet_data() 92 | { 93 | QTest::addColumn("path"); 94 | QTest::addColumn("err"); 95 | QTest::addColumn("body"); 96 | 97 | const QString errorTemplate = QLatin1String( 98 | "" 99 | "Error" 100 | "

%1

" 101 | ""); 102 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0)) 103 | int internalServerError = int(QNetworkReply::InternalServerError); 104 | #else 105 | int internalServerError = int(QNetworkReply::UnknownContentError); 106 | #endif 107 | QTest::newRow("root") << "/" << int(QNetworkReply::NoError) << QByteArray("method=GET|path=/"); 108 | QTest::newRow("query-string") << "/?message=bar" << int(QNetworkReply::NoError) << QByteArray("method=GET|path=/|get=bar"); 109 | QTest::newRow("not-found") << "/not-found" << int(QNetworkReply::ContentNotFoundError) << errorTemplate.arg(QLatin1String("The document you requested was not found.")).toUtf8(); 110 | QTest::newRow("internal-server-error") << "/internal-server-error" << internalServerError << errorTemplate.arg(QLatin1String("An internal server error was encountered.")).toUtf8(); 111 | } 112 | 113 | void tst_QDjangoHttpServer::testGet() 114 | { 115 | QFETCH(QString, path); 116 | QFETCH(int, err); 117 | QFETCH(QByteArray, body); 118 | 119 | QNetworkAccessManager network; 120 | QNetworkReply *reply = network.get(QNetworkRequest(QUrl(QLatin1String("http://127.0.0.1:8123") + path))); 121 | 122 | QEventLoop loop; 123 | QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); 124 | loop.exec(); 125 | 126 | QVERIFY(reply); 127 | QCOMPARE(int(reply->error()), err); 128 | QCOMPARE(reply->readAll(), body); 129 | delete reply; 130 | } 131 | 132 | void tst_QDjangoHttpServer::testPost_data() 133 | { 134 | QTest::addColumn("path"); 135 | QTest::addColumn("data"); 136 | QTest::addColumn("err"); 137 | QTest::addColumn("body"); 138 | 139 | QTest::newRow("empty") << "/" << QByteArray() << int(QNetworkReply::NoError) << QByteArray("method=POST|path=/"); 140 | QTest::newRow("simple") << "/" << QByteArray("message=bar") << int(QNetworkReply::NoError) << QByteArray("method=POST|path=/|post=bar"); 141 | QTest::newRow("multi") << "/" << QByteArray("bob=wiz&message=bar&zoo=wow") << int(QNetworkReply::NoError) << QByteArray("method=POST|path=/|post=bar"); 142 | } 143 | 144 | void tst_QDjangoHttpServer::testPost() 145 | { 146 | QFETCH(QString, path); 147 | QFETCH(QByteArray, data); 148 | QFETCH(int, err); 149 | QFETCH(QByteArray, body); 150 | 151 | QNetworkAccessManager network; 152 | QNetworkRequest req(QUrl(QLatin1String("http://127.0.0.1:8123") + path)); 153 | req.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); 154 | QNetworkReply *reply = network.post(req, data); 155 | 156 | QEventLoop loop; 157 | QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); 158 | loop.exec(); 159 | 160 | QVERIFY(reply); 161 | QCOMPARE(int(reply->error()), err); 162 | QCOMPARE(reply->readAll(), body); 163 | delete reply; 164 | } 165 | 166 | QDjangoHttpResponse *tst_QDjangoHttpServer::_q_index(const QDjangoHttpRequest &request) 167 | { 168 | QDjangoHttpResponse *response = new QDjangoHttpResponse; 169 | response->setHeader(QLatin1String("Content-Type"), QLatin1String("text/plain")); 170 | 171 | QString output = QLatin1String("method=") + request.method(); 172 | output += QLatin1String("|path=") + request.path(); 173 | 174 | const QString getValue = request.get(QLatin1String("message")); 175 | if (!getValue.isEmpty()) 176 | output += QLatin1String("|get=") + getValue; 177 | 178 | const QString postValue = request.post(QLatin1String("message")); 179 | if (!postValue.isEmpty()) 180 | output += QLatin1String("|post=") + postValue; 181 | 182 | response->setBody(output.toUtf8()); 183 | return response; 184 | } 185 | 186 | QDjangoHttpResponse *tst_QDjangoHttpServer::_q_error(const QDjangoHttpRequest &request) 187 | { 188 | Q_UNUSED(request); 189 | 190 | return QDjangoHttpController::serveInternalServerError(request); 191 | } 192 | 193 | QTEST_MAIN(tst_QDjangoHttpServer) 194 | #include "tst_qdjangohttpserver.moc" 195 | -------------------------------------------------------------------------------- /tests/http/qdjangourlresolver/qdjangourlresolver.pro: -------------------------------------------------------------------------------- 1 | include(../http.pri) 2 | 3 | TARGET = tst_qdjangourlresolver 4 | SOURCES += tst_qdjangourlresolver.cpp 5 | -------------------------------------------------------------------------------- /tests/http/qdjangourlresolver/tst_qdjangourlresolver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2015 Jeremy Lainé 3 | * Contact: https://github.com/jlaine/qdjango 4 | * 5 | * This file is part of the QDjango Library. 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "QDjangoHttpController.h" 25 | #include "QDjangoHttpRequest.h" 26 | #include "QDjangoHttpResponse.h" 27 | #include "QDjangoUrlResolver.h" 28 | 29 | class tst_QDjangoUrlHelper : public QObject 30 | { 31 | Q_OBJECT 32 | 33 | private slots: 34 | QDjangoHttpResponse* _q_index(const QDjangoHttpRequest &request); 35 | QDjangoHttpResponse* _q_test(const QDjangoHttpRequest &request); 36 | }; 37 | 38 | class tst_QDjangoUrlResolver : public QObject 39 | { 40 | Q_OBJECT 41 | 42 | private slots: 43 | void cleanupTestCase(); 44 | void initTestCase(); 45 | void testRespond_data(); 46 | void testRespond(); 47 | void testReverse_data(); 48 | void testReverse(); 49 | 50 | QDjangoHttpResponse* _q_index(const QDjangoHttpRequest &request); 51 | QDjangoHttpResponse* _q_noArgs(const QDjangoHttpRequest &request); 52 | QDjangoHttpResponse* _q_oneArg(const QDjangoHttpRequest &request, const QString &id); 53 | QDjangoHttpResponse* _q_twoArgs(const QDjangoHttpRequest &request, const QString &id, const QString &action); 54 | 55 | private: 56 | tst_QDjangoUrlHelper *urlHelper; 57 | QDjangoUrlResolver *urlResolver; 58 | QDjangoUrlResolver *urlSub; 59 | }; 60 | 61 | QDjangoHttpResponse* tst_QDjangoUrlHelper::_q_index(const QDjangoHttpRequest &request) 62 | { 63 | Q_UNUSED(request); 64 | 65 | QDjangoHttpResponse *response = new QDjangoHttpResponse; 66 | response->setHeader(QLatin1String("Content-Type"), QLatin1String("text/plain")); 67 | response->setBody("sub index"); 68 | return response; 69 | } 70 | 71 | QDjangoHttpResponse* tst_QDjangoUrlHelper::_q_test(const QDjangoHttpRequest &request) 72 | { 73 | Q_UNUSED(request); 74 | 75 | QDjangoHttpResponse *response = new QDjangoHttpResponse; 76 | response->setHeader(QLatin1String("Content-Type"), QLatin1String("text/plain")); 77 | response->setBody("sub test"); 78 | return response; 79 | } 80 | 81 | void tst_QDjangoUrlResolver::cleanupTestCase() 82 | { 83 | delete urlResolver; 84 | } 85 | 86 | void tst_QDjangoUrlResolver::initTestCase() 87 | { 88 | urlHelper = new tst_QDjangoUrlHelper; 89 | urlSub = new QDjangoUrlResolver; 90 | QVERIFY(urlSub->set(QRegExp(QLatin1String("^$")), urlHelper, "_q_index")); 91 | QVERIFY(urlSub->set(QRegExp(QLatin1String("^test/$")), urlHelper, "_q_test")); 92 | 93 | urlResolver = new QDjangoUrlResolver; 94 | QVERIFY(urlResolver->set(QRegExp(QLatin1String("^$")), this, "_q_index")); 95 | QVERIFY(urlResolver->set(QRegExp(QLatin1String("^test/$")), this, "_q_noArgs")); 96 | QVERIFY(urlResolver->set(QRegExp(QLatin1String("^test/([0-9]+)/$")), this, "_q_oneArg")); 97 | QVERIFY(urlResolver->set(QRegExp(QLatin1String("^test/([0-9]+)/([a-z]+)/$")), this, "_q_twoArgs")); 98 | QVERIFY(urlResolver->include(QRegExp(QLatin1String("^recurse/")), urlSub)); 99 | } 100 | 101 | void tst_QDjangoUrlResolver::testRespond_data() 102 | { 103 | QTest::addColumn("path"); 104 | QTest::addColumn("err"); 105 | QTest::addColumn("body"); 106 | 107 | QTest::newRow("root") << "/" << 200 << ""; 108 | QTest::newRow("not-found") << "/non-existent/" << 404 << ""; 109 | QTest::newRow("no-args") << "/test/" << 200 << ""; 110 | QTest::newRow("one-args") << "/test/123/" << 200 << ""; 111 | QTest::newRow("two-args") << "/test/123/delete/" << 200 << ""; 112 | QTest::newRow("three-args") << "/test/123/delete/zoo/" << 404 << ""; 113 | QTest::newRow("recurse-not-found") << "/recurse/non-existent/" << 404 << ""; 114 | QTest::newRow("recurse-index") << "/recurse/" << 200 << ""; 115 | QTest::newRow("recurse-test") << "/recurse/test/" << 200 << ""; 116 | } 117 | 118 | void tst_QDjangoUrlResolver::testRespond() 119 | { 120 | QFETCH(QString, path); 121 | QFETCH(int, err); 122 | QFETCH(QString, body); 123 | 124 | QDjangoHttpTestRequest request(QLatin1String("GET"), path); 125 | QDjangoHttpResponse *response = urlResolver->respond(request, path); 126 | QVERIFY(response); 127 | QCOMPARE(int(response->statusCode()), err); 128 | } 129 | 130 | void tst_QDjangoUrlResolver::testReverse_data() 131 | { 132 | QTest::addColumn("path"); 133 | QTest::addColumn("receiver"); 134 | QTest::addColumn("member"); 135 | QTest::addColumn("args"); 136 | QTest::addColumn("warning"); 137 | 138 | QObject *receiver = this; 139 | QTest::newRow("root") << "/" << receiver << "_q_index" << "" << ""; 140 | QTest::newRow("no-args") << "/test/" << receiver << "_q_noArgs" << "" << ""; 141 | QTest::newRow("one-arg") << "/test/123/" << receiver << "_q_oneArg" << "123" << ""; 142 | QTest::newRow("two-args") << "/test/123/delete/" << receiver << "_q_twoArgs" << "123|delete" << ""; 143 | QTest::newRow("too-few-args") << "" << receiver << "_q_oneArg" << "" << "Too few arguments for '_q_oneArg'"; 144 | QTest::newRow("too-many-args") << "" << receiver << "_q_noArgs" << "123" << "Too many arguments for '_q_noArgs'"; 145 | 146 | receiver = urlHelper; 147 | QTest::newRow("recurse-index") << "/recurse/" << receiver << "_q_index" << "" << ""; 148 | QTest::newRow("recurse-test") << "/recurse/test/" << receiver << "_q_test" << "" << ""; 149 | } 150 | 151 | void tst_QDjangoUrlResolver::testReverse() 152 | { 153 | QFETCH(QString, path); 154 | QFETCH(QObject*, receiver); 155 | QFETCH(QString, member); 156 | QFETCH(QString, args); 157 | QFETCH(QString, warning); 158 | 159 | QVariantList varArgs; 160 | if (!args.isEmpty()) { 161 | foreach (const QString &bit, args.split(QLatin1Char('|'))) 162 | varArgs << bit; 163 | } 164 | if (!warning.isEmpty()) 165 | QTest::ignoreMessage(QtWarningMsg, warning.toLatin1()); 166 | QCOMPARE(urlResolver->reverse(receiver, member.toLatin1(), varArgs), path); 167 | } 168 | 169 | QDjangoHttpResponse* tst_QDjangoUrlResolver::_q_index(const QDjangoHttpRequest &request) 170 | { 171 | Q_UNUSED(request); 172 | 173 | return new QDjangoHttpResponse; 174 | } 175 | 176 | QDjangoHttpResponse* tst_QDjangoUrlResolver::_q_noArgs(const QDjangoHttpRequest &request) 177 | { 178 | Q_UNUSED(request); 179 | 180 | return new QDjangoHttpResponse; 181 | } 182 | 183 | QDjangoHttpResponse* tst_QDjangoUrlResolver::_q_oneArg(const QDjangoHttpRequest &request, const QString &id) 184 | { 185 | Q_UNUSED(request); 186 | Q_UNUSED(id); 187 | 188 | return new QDjangoHttpResponse; 189 | } 190 | 191 | QDjangoHttpResponse* tst_QDjangoUrlResolver::_q_twoArgs(const QDjangoHttpRequest &request, const QString &id, const QString &action) 192 | { 193 | Q_UNUSED(request); 194 | Q_UNUSED(id); 195 | Q_UNUSED(action); 196 | 197 | return new QDjangoHttpResponse; 198 | } 199 | 200 | QTEST_MAIN(tst_QDjangoUrlResolver) 201 | #include "tst_qdjangourlresolver.moc" 202 | -------------------------------------------------------------------------------- /tests/tests.pri: -------------------------------------------------------------------------------- 1 | include(../qdjango.pri) 2 | 3 | QT -= gui 4 | QT += testlib 5 | CONFIG -= app_bundle 6 | CONFIG += testcase 7 | 8 | QMAKE_RPATHDIR += $$OUT_PWD/../../../src/db $$OUT_PWD/../../../src/http 9 | INCLUDEPATH += $$PWD $$QDJANGO_INCLUDEPATH 10 | -------------------------------------------------------------------------------- /tests/tests.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS = db http 3 | -------------------------------------------------------------------------------- /tests/travis/build-and-test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | QMAKE_ARGS="QDJANGO_PROFILE=true" 5 | 6 | case "$CONFIG" in 7 | *static) 8 | QMAKE_ARGS="$QMAKE_ARGS QDJANGO_LIBRARY_TYPE=staticlib" 9 | ;; 10 | esac 11 | 12 | # compile 13 | qmake $QMAKE_ARGS 14 | make 15 | 16 | # run tests 17 | QDJANGO_DB_DRIVER=QSQLITE QDJANGO_DB_NAME=:memory: make check 18 | QDJANGO_DB_DRIVER=QSQLITE QDJANGO_DB_NAME=/tmp/qdjango_test.db make check 19 | QDJANGO_DB_DRIVER=QMYSQL QDJANGO_DB_NAME=qdjango_test QDJANGO_DB_USER=root make check 20 | QDJANGO_DB_DRIVER=QPSQL QDJANGO_DB_NAME=qdjango_test QDJANGO_DB_USER=postgres make check 21 | exit 0 22 | 23 | # generate coverage report 24 | TRACEFILE=coverage.info 25 | rm -f $TRACEFILE 26 | lcov --capture --directory src -o $TRACEFILE --no-external 27 | lcov --remove $TRACEFILE \*moc_\* -o $TRACEFILE.clean 28 | mv $TRACEFILE.clean $TRACEFILE 29 | 30 | rm -rf coverage 31 | genhtml -o coverage $TRACEFILE 32 | echo "ok" 33 | --------------------------------------------------------------------------------