├── .gitignore ├── .travis.yml ├── CHANGES.rst ├── DEVELOPMENT.rst ├── DjangoLibrary ├── __init__.py ├── middleware.py └── tests │ ├── __init__.py │ ├── factories.py │ ├── test.robot │ ├── test_autologin.robot │ ├── test_factory_boy.robot │ ├── test_isolation.robot │ ├── test_query_set.robot │ └── test_selenium_bug_4198.robot.disabled ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── DjangoLibraryDocs.html └── index.html ├── kitconcept.png ├── mysite ├── bookstore │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── factories.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20160701_0830.py │ │ ├── 0003_auto_20160701_0845.py │ │ ├── 0004_auto_20160701_1711.py │ │ ├── 0005_auto_20160701_1859.py │ │ ├── 0006_auto_20160701_1900.py │ │ ├── 0007_auto_20160701_1900.py │ │ └── __init__.py │ ├── models.py │ ├── tests │ │ ├── __init__.py │ │ └── test_factory.py │ └── views.py ├── manage.py └── mysite │ ├── __init__.py │ ├── robotframework_settings.py │ ├── settings.py │ ├── tests │ ├── __init__.py │ ├── factories.py │ ├── test_factory_boy.py │ ├── test_middleware.py │ └── test_middleware_query.py │ ├── urls.py │ └── wsgi.py ├── pytest.ini ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .Python 3 | .vscode/ 4 | bin/ 5 | include/ 6 | lib/ 7 | man/ 8 | pip-selfcheck.json 9 | selenium/ 10 | .env/ 11 | log.html 12 | mysite/db.sqlite3 13 | *.egg-info 14 | .cache 15 | .py2*/ 16 | .py3*/ 17 | output.xml 18 | report.html 19 | selenium-screenshot* 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.7 4 | - 3.6 5 | - 3.7 6 | dist: xenial 7 | sudo: required 8 | addons: 9 | apt: 10 | sources: 11 | - google-chrome 12 | packages: 13 | - google-chrome-stable 14 | env: 15 | - DJANGO_VERSION=1.8.19 DB=SQLite 16 | - DJANGO_VERSION=1.11.17 DB=SQLite 17 | - DJANGO_VERSION=1.8.19 DB=Postgres 18 | - DJANGO_VERSION=1.11.17 DB=Postgres 19 | services: 20 | – postgresql 21 | addons: 22 | postgresql: '9.6' 23 | before_install: 24 | - sudo apt-get install -y xvfb 25 | install: 26 | - pip install -r requirements.txt 27 | - pip install -q flake8 28 | - pip install -q psycopg2 29 | - pip install -q --pre Django==$DJANGO_VERSION 30 | - python setup.py install 31 | before_script: 32 | - wget "http://chromedriver.storage.googleapis.com/2.35/chromedriver_linux64.zip" 33 | - unzip chromedriver_linux64.zip 34 | - sudo mv chromedriver /usr/local/bin 35 | - "export DISPLAY=:99.0" 36 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 37 | - psql -c 'create database travis_ci_test;' -U postgres 38 | - psql -c 'create database robotframework;' -U postgres 39 | # - django-admin startproject mysite 40 | script: 41 | - flake8 DjangoLibrary 42 | - py.test mysite 43 | - robot DjangoLibrary/tests/ 44 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | 2 | 3.1.1 (unreleased) 3 | ------------------ 4 | 5 | - Nothing changed yet. 6 | 7 | 8 | 3.1.0 (2018-12-24) 9 | ------------------ 10 | 11 | New Features: 12 | 13 | - Python 3.7 support. 14 | [timo] 15 | 16 | 17 | 3.0.0 (2018-11-26) 18 | ------------------ 19 | 20 | Breaking Changes: 21 | 22 | - Depend on SeleniumLibrary instead of Selenium2Library. 23 | The name changed from Selenium2Library to SeleniumLibrary. 24 | See https://github.com/robotframework/SeleniumLibrary/blob/master/docs/SeleniumLibrary-3.0.0.rst#name-changed-from-selenium2library-to-seleniumlibrary 25 | for details. 26 | [timo] 27 | 28 | New Features: 29 | 30 | - Python 3.6 support (earlier versions most likely do support Python 3.6 as well, we just did not test it so far). 31 | [timo] 32 | 33 | 34 | 2.0.2 (2018-03-03) 35 | ------------------ 36 | 37 | Bugfixes / Minor Changes: 38 | 39 | - Fix Pypi markup. 40 | [timo] 41 | 42 | 43 | 2.0.1 (2018-03-03) 44 | ------------------ 45 | 46 | Bugfixes / Minor Changes: 47 | 48 | - Fix outdated documentation. 49 | [timo] 50 | 51 | 52 | 2.0 (2017-09-20) 53 | ---------------- 54 | 55 | Breaking Changes: 56 | 57 | - Remove deprecated method from internal startup flow. 58 | [cdvv7788] 59 | 60 | New Features: 61 | 62 | - Use AUTH_MODEL to create user instead of django's default 63 | [cdvv7788] 64 | 65 | - Upgrade FactoryBoy to version 2.8.1 66 | [amarandon] 67 | 68 | - Add Django 1.10.7 and 1.11.1 support. 69 | [timo] 70 | 71 | - LICENSE.txt added. 72 | [timo] 73 | 74 | Bugfixes / Minor Changes: 75 | 76 | - Move tests to use Chrome. 77 | [timo] 78 | 79 | - Upgrade tests to Django 1.11.5, 1.10.8, 1.9.13, 1.8.18. 80 | [timo] 81 | 82 | - Add Django 1.11 and 1.10 to setup.py. 83 | [timo] 84 | 85 | 86 | 1.2 (2016-07-08) 87 | ---------------- 88 | 89 | New Features: 90 | 91 | - Make Factory Boy keyword return 'pk' attribute. 92 | [timo] 93 | 94 | 95 | 1.1 (2016-07-07) 96 | ---------------- 97 | 98 | New Features: 99 | 100 | - Add QuerySet keyword. 101 | [timo] 102 | 103 | - Make it possible to override subfactories when using the `FactoryBoy` 104 | keyword. 105 | [timo] 106 | 107 | Bugfixes: 108 | 109 | - Use Django's model_to_dict method to serialize the response objects for the 110 | factory_boy keyword. 111 | [timo] 112 | 113 | 114 | 1.0 (2016-06-30) 115 | ---------------- 116 | 117 | - Re-release 1.0a6 as 1.0. 118 | [timo] 119 | 120 | 121 | 1.0a6 (2016-04-29) 122 | ------------------ 123 | 124 | New Features: 125 | 126 | - Python 3 compatibility. Note that the latest offical release of 127 | robotframework-selenium2library is currently not compatible with Python 3. 128 | See https://github.com/HelioGuilherme66/robotframework-selenium2library/releases for a working pre-release and details. 129 | [timo] 130 | 131 | - Support for Postgres added. All Django database backends should work. 132 | We test SQLite and Postgres only though. 133 | [timo] 134 | 135 | - Add 'Factory Boy' keyword. This allows us to use factory_boy factories in 136 | Robot Framework tests. 137 | [timo] 138 | 139 | Breaking Changes: 140 | 141 | - Drop Django 1.7.x support. We test and support Django 1.8.x and 1.9.x. 142 | [timo] 143 | 144 | - Change 'Clear DB' implementation to use "python manage.py flush" instead of 145 | deleting and re-building the database. 146 | [timo] 147 | 148 | - Remove 'Debug' and 'Pause' keywords. The 'Debug' keyword, which is 149 | provided by robotframework-debuglibrary is sufficient. 150 | [timo] 151 | 152 | 153 | 1.0a5 (2016-02-11) 154 | ------------------ 155 | 156 | - Make middleware part Python 3 compatible. 157 | 158 | robotframework-djangolibrary is still not compatible with Python 3 because 159 | robotframework-selenium2library does not work with Python 3 yet. Though, you 160 | can install robotframwork-djangolibrary on Python 3 with "pip install 161 | robotframework-djangolibrary --no-deps" and then run your tests with 162 | Python 2.7. 163 | [timo] 164 | 165 | - Add 'Framework :: Robot Framework' classifier to setup.py. 166 | [timo] 167 | 168 | 169 | 1.0a4 (2016-02-05) 170 | ------------------ 171 | 172 | - Use 'migrate' instead of 'syncdb' for Django > 1.7.x. 173 | [timo] 174 | 175 | 176 | 1.0a3 (2015-09-28) 177 | ------------------ 178 | 179 | - Add list_classifiers to setup.py. 180 | [timo] 181 | 182 | - Fix user creation and startup. This fixes #3. 183 | [MatthewWilkes] 184 | 185 | 186 | 1.0a2 (2015-06-25) 187 | ------------------ 188 | 189 | - Remove Django and zest.releaser from requirements.txt. This fixes #2. 190 | [timo] 191 | 192 | 193 | 1.0a1 (2015-06-24) 194 | ------------------ 195 | 196 | - Initial release. 197 | [timo] 198 | -------------------------------------------------------------------------------- /DEVELOPMENT.rst: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | Development Documentation 3 | ============================================================================== 4 | 5 | Set up development environment 6 | ------------------------------ 7 | 8 | $ virtualenv .env 9 | $ source .env/bin/activate 10 | $ python setup.py develop 11 | 12 | Start 'MySite' Django application that is used to run the tests against:: 13 | 14 | $ pip install Django 15 | $ python mysite/manage.py makemigrations 16 | $ python mysite/manage.py migrate 17 | $ python mysite/manage.py runserver 18 | 19 | Run DjangoLibrary tests:: 20 | 21 | $ python setup.py develop 22 | $ robot DjangoLibrary/tests/autologin.robot 23 | 24 | 25 | Generate Documentation 26 | ---------------------- 27 | 28 | $ python -m robot.libdoc DjangoLibrary docs/DjangoLibrary.html 29 | 30 | Release Documentation: 31 | 32 | $ git checkout gh-pages 33 | $ git commit . -m"Update keyword docs." 34 | $ git push 35 | $ git checkout master 36 | 37 | Make Release 38 | ------------ 39 | 40 | $ pip install zest.releaser 41 | $ fullrelease 42 | 43 | 44 | Further Reading 45 | --------------- 46 | 47 | http://robotframework.googlecode.com/hg/doc/userguide/RobotFrameworkUserGuide.html?r=2.8.3#creating-test-libraries 48 | 49 | https://docs.djangoproject.com/en/dev/howto/auth-remote-user/ 50 | -------------------------------------------------------------------------------- /DjangoLibrary/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from robot.api import logger 3 | from robot.libraries.BuiltIn import BuiltIn 4 | from warnings import warn 5 | 6 | import base64 7 | import json 8 | import os 9 | import requests 10 | import signal 11 | import six 12 | import subprocess 13 | 14 | __version__ = '1.0' 15 | ROBOT_LIBRARY_DOC_FORMAT = 'reST' 16 | 17 | 18 | def safe_bytes(str): 19 | """Returns bytes on Py3 and a string on Py2.""" 20 | if six.PY3: 21 | return bytes(str, 'utf-8') 22 | else: 23 | return str 24 | 25 | 26 | def safe_utf8(string): 27 | """Returns bytes on Py3 and an utf-8 encoded string on Py2.""" 28 | if six.PY2: 29 | return string.encode("utf-8") 30 | else: 31 | return string 32 | 33 | 34 | class DjangoLibrary: 35 | """DjangoLibrary is a web testing library to test Django with Robot 36 | Framework. 37 | 38 | It uses SeleniumLibrary to run tests against a real browser instance. 39 | 40 | *Before running tests* 41 | 42 | Prior to running test cases using DjangoLibrary, DjangoLibrary must be 43 | imported (together with SeleniumLibrary) into your Robot test suite 44 | (see `importing` section), and the SeleniumLibrary 'Open Browser' keyword 45 | must be used to open a browser to the desired location. 46 | """ 47 | 48 | django_pid = None 49 | 50 | # TEST CASE => New instance is created for every test case. 51 | # TEST SUITE => New instance is created for every test suite. 52 | # GLOBAL => Only one instance is created during the whole test execution. 53 | ROBOT_LIBRARY_SCOPE = 'TEST SUITE' 54 | 55 | def __init__(self, host="0.0.0.0", port=8000, path='mysite/mysite', 56 | manage='mysite/manage.py', settings='mysite.settings', 57 | db=None): 58 | """Django2Library can be imported with optional arguments. 59 | 60 | `host` is the hostname of your Django instance. Default value is 61 | '127.0.0.1'. 62 | 63 | `port` is the port number of your Django instance. Default value is 64 | 8000. 65 | 66 | `path` is the path to your Django instance. 67 | 68 | `manage` is the path to your Django instance manage.py. 69 | 70 | `settings` is the path to your Django instance settings.py. 71 | 72 | `db` is deprecated. Please don't use it. 73 | 74 | Examples: 75 | | Library | SeleniumLibrary | timeout=15 | implicit_wait=0.5 | # Sets default timeout to 15 seconds and the default implicit_wait to 0.5 seconds. | # noqa 76 | | Library | DjangoLibrary | 127.0.0.1 | 55001 | path=mysite/mysite | manage=mysite/manage.py | settings=mysite.settings | db=mysite/db.sqlite3 | # Sets default hostname to 127.0.0.1 and the default port to 55001. | # noqa 77 | """ 78 | self.host = host 79 | self.port = port 80 | self.path = os.path.realpath(path) 81 | self.manage = os.path.realpath(manage) 82 | self.settings = settings 83 | if db: 84 | warn( 85 | "Using the DjangoLibrary 'db' parameter is deprecated. " + 86 | "Use the 'settings' parameter instead to set a " + 87 | "database connection." 88 | ) 89 | 90 | def manage_makemigrations(self): 91 | """Create migrations by running 'python manage.py makemigrations'.""" 92 | args = [ 93 | 'python', 94 | self.manage, 95 | 'makemigrations', 96 | ] 97 | subprocess.call(args) 98 | 99 | def manage_migrate(self): 100 | """Execute migration by running 'python manage.py migrate'.""" 101 | args = [ 102 | 'python', 103 | self.manage, 104 | 'migrate', 105 | '--settings=%s' % self.settings, 106 | ] 107 | subprocess.call(args) 108 | 109 | def manage_flush(self): 110 | """Clear database by running 'python manage.py flush'.""" 111 | args = [ 112 | 'python', 113 | self.manage, 114 | 'flush', 115 | '--noinput', 116 | '--settings=%s' % self.settings, 117 | ] 118 | subprocess.call(args) 119 | 120 | def clear_db(self): 121 | """Clear database. This is a legacy keyword now. Use 'Manage Flush' 122 | instead. 123 | """ 124 | warn( 125 | "The DjangoLibrary 'clear_db' keyword is deprecated. " + 126 | "Use the 'manage_flush' keyword instead." 127 | ) 128 | self.manage_flush() 129 | 130 | def create_user(self, username, email, password, **kwargs): 131 | """Create a regular Django user in the default auth model. 132 | 133 | The `Create User` keyword allows to provide additional arguments that 134 | are passed directly to the Djange create_user method (e.g. 135 | "is_staff=True").""" 136 | 137 | to_run = """ 138 | from django.contrib.auth import get_user_model 139 | user = get_user_model().objects.create_user( 140 | '{0}', 141 | email='{1}', 142 | password='{2}', 143 | ) 144 | user.is_superuser = '{3}' 145 | user.is_staff = '{4}' 146 | user.save()""".format( 147 | safe_utf8(username), 148 | safe_utf8(email), 149 | safe_utf8(password), 150 | kwargs.get('is_superuser', False), 151 | kwargs.get('is_staff', False), 152 | ) 153 | args = [ 154 | 'python', 155 | self.manage, 156 | 'shell', 157 | '--plain', 158 | '--settings=%s' % self.settings, 159 | ] 160 | 161 | django = subprocess.Popen( 162 | args, 163 | stdin=subprocess.PIPE, 164 | stdout=subprocess.PIPE, 165 | stderr=subprocess.PIPE 166 | ) 167 | 168 | django.communicate(safe_bytes(to_run)) 169 | 170 | def create_superuser(self, username, email, password): 171 | """Create a Django superuser in the default auth model.""" 172 | self.create_user(username, email, password, 173 | is_superuser=True, is_staff=True) 174 | 175 | def start_django(self): 176 | """Start the Django server.""" 177 | self.manage_flush() 178 | self.manage_makemigrations() 179 | self.manage_migrate() 180 | logger.console("-" * 78) 181 | args = [ 182 | 'python', 183 | self.manage, 184 | 'runserver', 185 | '%s:%s' % (self.host, self.port), 186 | '--nothreading', 187 | '--noreload', 188 | '--settings=%s' % self.settings, 189 | ] 190 | 191 | self.django_pid = subprocess.Popen( 192 | args, 193 | stdout=subprocess.PIPE, 194 | stderr=subprocess.PIPE 195 | ).pid 196 | logger.console( 197 | "Django started (PID: %s)" % self.django_pid, 198 | ) 199 | logger.console("-" * 78) 200 | 201 | def stop_django(self): 202 | """Stop the Django server.""" 203 | os.kill(self.django_pid, signal.SIGKILL) 204 | logger.console( 205 | "Django stopped (PID: %s)" % self.django_pid, 206 | ) 207 | logger.console("-" * 78) 208 | 209 | def autologin_as(self, username, password): 210 | """Autologin as Django user. 211 | 212 | DjangoLibrary comes with a Django middleware component that allows the 213 | autologin_as keyword to set an 'autologin' cookie that the 214 | middleware uses to authenticate and login the user in Django. 215 | 216 | If you want to use the autlogin_as keyword you have to add 217 | 'DjangoLibrary.middleware.AutologinAuthenticationMiddleware' to the 218 | MIDDLEWARE_CLASSES right after the default AuthenticationMiddleware 219 | in your settings.py:: 220 | 221 | MIDDLEWARE_CLASSES = ( 222 | ... 223 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 224 | 'DjangoLibrary.middleware.AutologinAuthenticationMiddleware', 225 | ) 226 | 227 | *Warning* 228 | 229 | Make sure that you add this middleware only to your test setup and 230 | NEVER to your deployment! 231 | 232 | See https://github.com/kitconcept/robotframework-djangolibrary/blob/master/DjangoLibrary/tests/test_autologin.robot # noqa 233 | for examples how to use the `Autologin As` keyword. 234 | 235 | """ 236 | if six.PY2: 237 | username = username.encode('utf-8') 238 | password = password.encode('utf-8') 239 | # encode autologin cookie value as base64 240 | autologin_cookie_value = base64.b64encode( 241 | safe_bytes("%s:%s" % (username, password)) 242 | ) 243 | 244 | selenium2lib = BuiltIn().get_library_instance('SeleniumLibrary') 245 | # XXX: The 'Add Cookie' keywords does not work with Firefox, therefore 246 | # we have to add the cookie with js here. A bug has been filed: 247 | # https://github.com/rtomac/robotframework-selenium2library/issues/273 248 | # selenium2lib.add_cookie( 249 | # "autologin", 250 | # "%s:%s" % (username, password), 251 | # path="/", 252 | # domain="localhost", 253 | # ) 254 | 255 | if six.PY3: 256 | selenium2lib.execute_javascript( 257 | "document.cookie = 'autologin=%s;path=/;domain=localhost;';" % 258 | autologin_cookie_value.decode('utf-8') 259 | ) 260 | else: 261 | selenium2lib.execute_javascript( 262 | "document.cookie = 'autologin=%s;path=/;domain=localhost;';" % 263 | autologin_cookie_value 264 | ) 265 | 266 | # autologin_cookie = selenium2lib.get_cookie_value('autologin') 267 | # assert autologin_cookie == "%s:%s" % (username, password) 268 | # cookies = selenium2lib.get_cookies() 269 | # assert cookies == u"autologin=%s:%s" % (username, password) 270 | 271 | def autologin_logout(self): 272 | """Logout a user that has been logged in by the autologin_as keyword. 273 | """ 274 | selenium2lib = BuiltIn().get_library_instance('SeleniumLibrary') 275 | selenium2lib.execute_javascript( 276 | "document.cookie = 'autologin=;path=/;domain=localhost;';" 277 | ) 278 | 279 | def factory_boy(self, factory, **kwargs): 280 | """Create content objects in the Django database with Factory Boy. 281 | See https://factoryboy.readthedocs.org for more details. 282 | 283 | Arguments: 284 | 285 | `factory` is a required argument and should contain the full path to 286 | your factory boy factory class (e.g. 287 | "FactoryBoy mysite.polls.factories.PollFactory"). 288 | 289 | The `Factory Boy` keyword allows to provide additional arguments that 290 | are passed directly to the Factory Boy Factory class 291 | (e.g. "FactoryBoy mysite.polls.factories.PollFactory pollname='mypoll'"). 292 | 293 | You can also override subfactories by using the double-underscore 294 | field lookup (https://docs.djangoproject.com/en/1.9/topics/db/queries/#field-lookups) #noqa 295 | together with the `pk` lookup shortcut (https://docs.djangoproject.com/en/1.9/topics/db/queries/#the-pk-lookup-shortcut) 296 | (e.g. "Factory Boy bookstore.factories.BookFactory ... author__pk=1") 297 | 298 | See https://github.com/kitconcept/robotframework-djangolibrary/blob/master/DjangoLibrary/tests/test_factory_boy.robot # noqa 299 | for examples how to use the `Factory Boy` keyword. 300 | 301 | """ 302 | url = 'http://{}:{}'.format( 303 | self.host, 304 | self.port 305 | ) 306 | payload = { 307 | 'FACTORY_BOY_MODEL_PATH': factory, 308 | 'FACTORY_BOY_ARGS': json.dumps(kwargs) 309 | } 310 | response = requests.get(url, params=payload) 311 | if response.status_code == 201: 312 | return response.json() 313 | status_code_400 = response.status_code == 400 314 | type_json = response.headers.get('Content-Type') == 'application/json' 315 | if status_code_400 and type_json: 316 | msg = response.json().get('error', '') 317 | traceback = response.json().get('traceback', '') 318 | if traceback: 319 | msg = msg + '\n\n' + traceback 320 | raise requests.exceptions.HTTPError(msg, response=response) 321 | return response.raise_for_status() 322 | 323 | def query_set(self, model, **kwargs): 324 | """Query the Django ORM. 325 | 326 | Returns a QuerySet object. See https://docs.djangoproject.com/en/1.9/topics/db/queries/#retrieving-objects for details. # noqa 327 | 328 | Arguments: 329 | 330 | `model` is a required argument and should contain the full path to 331 | your Django model class (e.g. "django.contrib.auth.models.User"). 332 | 333 | The `QuerySet` keyword allows to provide additional arguments that 334 | are passed as filter arguments 335 | (e.g. "django.contrib.auth.models.User username=john"). 336 | If no additonal argument is provided `QuerySet will just return all 337 | objects that exists for that model. 338 | 339 | `limit` limits the number of results, 340 | e.g. "django.contrib.auth.models.User limit=10" will return 10 results 341 | max. 342 | Limit is an optional argument that maps 1:1 to the QuerySet limit 343 | argument. See https://docs.djangoproject.com/en/1.9/topics/db/queries/#limiting-querysets # noqa 344 | for details. 345 | 346 | `offset` can be used in combination with `limit` to set an offset, 347 | e.g. "django.contrib.auth.models.User offeset=5 limit=10" will return 348 | 5 results while omitting the first 5 results. 349 | See https://docs.djangoproject.com/en/1.9/topics/db/queries/#limiting-querysets # noqa 350 | for details. 351 | 352 | See https://github.com/kitconcept/robotframework-djangolibrary/blob/master/DjangoLibrary/tests/test_query_set.robot # noqa 353 | for examples how to use the `Query Set` keyword. 354 | 355 | """ 356 | url = 'http://{}:{}'.format( 357 | self.host, 358 | self.port 359 | ) 360 | payload = { 361 | 'MODEL_PATH': model, 362 | 'QUERY_ARGS': json.dumps(kwargs) 363 | } 364 | response = requests.get(url, params=payload) 365 | if response.status_code == 200: 366 | return response.json() 367 | status_code_400 = response.status_code == 400 368 | type_json = response.headers.get('Content-Type') == 'application/json' 369 | if status_code_400 and type_json: 370 | msg = response.json().get('error', '') 371 | traceback = response.json().get('traceback', '') 372 | if traceback: 373 | msg = msg + '\n\n' + traceback 374 | raise requests.exceptions.HTTPError(msg, response=response) 375 | return response.raise_for_status() 376 | -------------------------------------------------------------------------------- /DjangoLibrary/middleware.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from django.contrib import auth 3 | from django.contrib.auth.middleware import AuthenticationMiddleware 4 | from django.db.models import ForeignKey 5 | from django.core import serializers 6 | from django.http import JsonResponse 7 | from pydoc import locate 8 | 9 | import base64 10 | import json 11 | 12 | 13 | def model_to_dict(model): 14 | serialized_obj = serializers.serialize('python', [model]) 15 | serialized_obj = serialized_obj[0] 16 | serialized_obj = dict(serialized_obj) 17 | for key, value in serialized_obj.items(): 18 | if isinstance(value, OrderedDict): 19 | serialized_obj[key] = dict(value) 20 | serialized_obj = serialized_obj['fields'] 21 | serialized_obj['id'] = model.id 22 | serialized_obj['pk'] = model.pk 23 | return serialized_obj 24 | 25 | 26 | class AutologinAuthenticationMiddleware(AuthenticationMiddleware): 27 | 28 | def process_request(self, request): 29 | if 'autologin' not in request.COOKIES: 30 | return 31 | if request.COOKIES['autologin'] == '': 32 | auth.logout(request) 33 | return 34 | autologin_cookie_value = base64.b64decode(request.COOKIES['autologin']) 35 | # Py3 uses a bytes string here, so we need to decode to utf-8 36 | autologin_cookie_value = autologin_cookie_value.decode('utf-8') 37 | username = autologin_cookie_value.split(':')[0] 38 | password = autologin_cookie_value.split(':')[1] 39 | user = auth.authenticate(username=username, password=password) 40 | if user is not None: 41 | if user.is_active: 42 | auth.login(request, user) 43 | 44 | 45 | class FactoryBoyMiddleware(): 46 | 47 | def _get_foreign_key_fields(self, FactoryBoyClass): 48 | foreign_key_fields = [] 49 | for field in FactoryBoyClass._meta.model._meta.fields: 50 | if isinstance(field, ForeignKey): 51 | foreign_key_fields.append(field) 52 | return foreign_key_fields 53 | 54 | def _foreign_key_to_model(self, FactoryBoyClass, factory_boy_args): 55 | if not hasattr(FactoryBoyClass, '_meta'): 56 | return 57 | if not hasattr(FactoryBoyClass._meta.model, '_meta'): 58 | return 59 | for field in self._get_foreign_key_fields(FactoryBoyClass): 60 | for key, value in factory_boy_args.items(): 61 | key_name = '{}__pk'.format(field.name) 62 | if key == key_name: 63 | RelModel = field.foreign_related_fields[0].model 64 | del factory_boy_args[key_name] 65 | new_key = key_name.replace('__pk', '') 66 | factory_boy_args[new_key] = RelModel.objects.first() 67 | 68 | def process_request(self, request): 69 | model_name = request.GET.get('FACTORY_BOY_MODEL_PATH') 70 | if not model_name: 71 | return 72 | get_args = request.GET.get('FACTORY_BOY_ARGS') 73 | try: 74 | factory_boy_args = json.loads(get_args) 75 | except ValueError: 76 | factory_boy_args = {} 77 | FactoryBoyClass = locate(model_name) 78 | if not FactoryBoyClass: 79 | msg = 'Factory Boy class "{}" could not be found' 80 | return JsonResponse( 81 | { 82 | 'error': msg.format(model_name) 83 | }, 84 | status=400 85 | ) 86 | self._foreign_key_to_model(FactoryBoyClass, factory_boy_args) 87 | try: 88 | obj = FactoryBoyClass(**factory_boy_args) 89 | except: # noqa 90 | return JsonResponse( 91 | { 92 | 'error': 'FactoryBoyClass "{}" '.format(model_name) + 93 | 'could not be instantiated with args "{}"'.format( 94 | factory_boy_args 95 | ) 96 | }, 97 | status=400 98 | ) 99 | obj_meta = getattr(obj, '_meta', None) 100 | if not obj_meta: 101 | return JsonResponse( 102 | { 103 | 'error': 'The FactoryBoyClass "{}" '.format(model_name) + 104 | 'instance does not seem to provide a _meta attribute. ' + 105 | 'Please check if the Factory Boy class inherits from ' + 106 | 'DjangoModelFactory' 107 | }, 108 | status=400 109 | ) 110 | return JsonResponse(model_to_dict(obj), status=201) 111 | 112 | 113 | class QuerySetMiddleware(): 114 | 115 | def process_request(self, request): 116 | model_name = request.GET.get('MODEL_PATH') 117 | if not model_name: 118 | return 119 | get_args = request.GET.get('QUERY_ARGS') 120 | try: 121 | query_args = json.loads(get_args) 122 | except (ValueError, TypeError): 123 | query_args = {} 124 | ModelClass = locate(model_name) 125 | if not ModelClass: 126 | msg = 'Class "{}" could not be found' 127 | return JsonResponse( 128 | { 129 | 'error': msg.format(model_name) 130 | }, 131 | status=400 132 | ) 133 | result = [] 134 | limit = None 135 | if 'limit' in query_args: 136 | limit = query_args['limit'] 137 | del query_args['limit'] 138 | offset = None 139 | if 'offset' in query_args: 140 | offset = query_args['offset'] 141 | del query_args['offset'] 142 | if query_args: 143 | try: 144 | objects = ModelClass.objects.filter(**query_args) 145 | except ModelClass.DoesNotExist: 146 | objects = [] 147 | else: 148 | objects = ModelClass.objects.all() 149 | if offset and limit: 150 | objects = objects[int(offset):int(limit)] 151 | elif not offset and limit: 152 | objects = objects[:int(limit)] 153 | for obj in objects: 154 | serialized_obj = model_to_dict(obj) 155 | serialized_obj['pk'] = obj.pk 156 | result.append(serialized_obj) 157 | return JsonResponse(result, safe=False, status=200) 158 | -------------------------------------------------------------------------------- /DjangoLibrary/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitconcept/robotframework-djangolibrary/fda7454482b889a90b727adf7444929c3b597fa3/DjangoLibrary/tests/__init__.py -------------------------------------------------------------------------------- /DjangoLibrary/tests/factories.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.template.defaultfilters import slugify 3 | from factory import DjangoModelFactory, lazy_attribute 4 | from random import randint 5 | 6 | 7 | class UserFactory(DjangoModelFactory): 8 | class Meta: 9 | model = 'auth.User' 10 | django_get_or_create = ('username',) 11 | 12 | first_name = 'John' 13 | last_name = 'Doe' 14 | username = lazy_attribute( 15 | lambda o: slugify(o.first_name + '.' + o.last_name) 16 | ) 17 | email = lazy_attribute(lambda o: o.username + "@example.com") 18 | 19 | @lazy_attribute 20 | def date_joined(self): 21 | return datetime.datetime.now() - datetime.timedelta( 22 | days=randint(5, 50) 23 | ) 24 | last_login = lazy_attribute( 25 | lambda o: o.date_joined + datetime.timedelta(days=4) 26 | ) 27 | 28 | 29 | class BrokenFactory(DjangoModelFactory): 30 | 31 | class Meta: 32 | model = 'nonExistingModel' 33 | 34 | 35 | class BrokenFactoryWithoutMetaClass(DjangoModelFactory): 36 | pass 37 | 38 | 39 | class BrokenFactoryClassDoesNotInheritFromDjangoModelFactory(): 40 | class Meta: 41 | model = 'auth.User' 42 | -------------------------------------------------------------------------------- /DjangoLibrary/tests/test.robot: -------------------------------------------------------------------------------- 1 | *** Variables *** 2 | 3 | ${SERVER} http://localhost:55001 4 | ${BROWSER} chrome 5 | 6 | 7 | *** Settings *** 8 | 9 | Documentation Django Robot Tests 10 | Library SeleniumLibrary timeout=10 implicit_wait=0.5 11 | Library DjangoLibrary 127.0.0.1 55001 settings=mysite.robotframework_settings 12 | Suite Setup Start Django and Open Browser 13 | Suite Teardown Stop Django and Close Browser 14 | 15 | 16 | *** Keywords *** 17 | 18 | Start Django and open Browser 19 | Start Django 20 | Open Browser ${SERVER} ${BROWSER} 21 | 22 | Stop Django and close browser 23 | Close Browser 24 | Stop Django 25 | 26 | 27 | *** Test Cases *** 28 | 29 | Scenario: As a visitor I can visit the django default page 30 | Go To ${SERVER} 31 | Wait until page contains element id=explanation 32 | Page Should Contain It worked! 33 | Page Should Contain Congratulations on your first Django-powered page. 34 | 35 | Scenario: As a visitor I can visit the django admin login page 36 | Go To ${SERVER}/admin 37 | Wait until page contains Django administration 38 | Page Should Contain Django administration 39 | Page Should Contain Element name=username 40 | Page Should Contain Element name=password 41 | -------------------------------------------------------------------------------- /DjangoLibrary/tests/test_autologin.robot: -------------------------------------------------------------------------------- 1 | *** Variables *** 2 | 3 | ${SERVER} http://localhost:55001 4 | ${BROWSER} chrome 5 | 6 | 7 | *** Settings *** 8 | 9 | Documentation DjangoLibrary Autologin Tests 10 | Library SeleniumLibrary timeout=10 implicit_wait=0 11 | Library DjangoLibrary 127.0.0.1 55001 path=mysite/mysite manage=mysite/manage.py settings=mysite.robotframework_settings 12 | Suite Setup Start Django and Open Browser 13 | Suite Teardown Stop Django and Close Browser 14 | 15 | 16 | *** Test Cases *** 17 | 18 | Create superuser keyword should create superuser 19 | Create Superuser admin admin@admin.com password 20 | Go To ${SERVER}/admin 21 | Wait until page contains Django administration 22 | Input text username admin 23 | Input text password password 24 | Click Button Log in 25 | Wait until page contains Django administration 26 | Page should contain Django administration 27 | Page should not contain Please enter the correct username and password 28 | Logout 29 | 30 | Create superuser keyword should work with special characters 31 | Create Superuser admin-öüä admin@admin.com password 32 | Go To ${SERVER}/admin 33 | Wait until page contains Django administration 34 | Input text username admin-öüä 35 | Input text password password 36 | Click Button Log in 37 | Wait until page contains Django administration 38 | Page should contain Django administration 39 | Page should not contain Please enter the correct username and password 40 | Logout 41 | 42 | Create user keyword should create user 43 | Create User test-user-1 test@test.com password is_superuser=True is_staff=True 44 | Go To ${SERVER}/admin 45 | Wait until page contains Django administration 46 | Input text username test-user-1 47 | Input text password password 48 | Click Button Log in 49 | Wait until page contains Django administration 50 | Page should contain Django administration 51 | Page should not contain Please enter the correct username and password 52 | Logout 53 | 54 | Create user keyword should work with special characters 55 | Create User äüö-AÜÖ test@test.com password is_superuser=True is_staff=True 56 | Go To ${SERVER}/admin 57 | Wait until page contains Django administration 58 | Input text username äüö-AÜÖ 59 | Input text password password 60 | Click Button Log in 61 | Wait until page contains Django administration 62 | Page should contain Django administration 63 | Page should not contain Please enter the correct username and password 64 | Logout 65 | 66 | Autologin keyword should login user 67 | Create User test-user-2 test@test.com password is_superuser=True is_staff=True 68 | Autologin as test-user-2 password 69 | User is logged in 70 | 71 | Autologin keyword should login user with special characters 72 | [tags] current 73 | Create User äüö-AÜÖ test@test.com password is_superuser=True is_staff=True 74 | Autologin as äüö-AÜÖ password 75 | User is logged in 76 | 77 | Autologin keyword should work multiple times 78 | Autologin as test-user-1 password 79 | Autologin as test-user-2 password 80 | User 'test-user-2' is logged in 81 | 82 | Autologin Logout keywords should log out user 83 | Create User test-user-3 test@test.com password is_superuser=True is_staff=True 84 | Autologin as test-user-3 password 85 | User is logged in 86 | Autologin Logout 87 | User is logged out 88 | 89 | 90 | *** Keywords *** 91 | 92 | Start Django and open Browser 93 | Start Django 94 | Open Browser ${SERVER} ${BROWSER} 95 | 96 | Stop Django and close browser 97 | Close Browser 98 | Stop Django 99 | 100 | Logout 101 | Go To ${SERVER}/admin/logout 102 | Wait until page contains Logged out 103 | 104 | User is logged in 105 | Go To ${SERVER}/admin 106 | Page should contain Site administration message=User is not logged in 107 | 108 | User '${username}' is logged in 109 | Go To ${SERVER}/admin 110 | Page should contain ${username} message=User '${username}' is not logged in 111 | 112 | User is logged out 113 | Go To ${SERVER}/admin 114 | Page should not contain Site administration message=User is not logged out 115 | -------------------------------------------------------------------------------- /DjangoLibrary/tests/test_factory_boy.robot: -------------------------------------------------------------------------------- 1 | *** Variables *** 2 | 3 | ${SERVER} http://localhost:55001 4 | ${BROWSER} chrome 5 | 6 | 7 | *** Settings *** 8 | 9 | Documentation Testing Test Isolation 10 | Library SeleniumLibrary timeout=10 implicit_wait=0 11 | Library DjangoLibrary 127.0.0.1 55001 settings=mysite.robotframework_settings 12 | Library Collections 13 | Library DebugLibrary 14 | Suite Setup Start Django and Open Browser 15 | Suite Teardown Stop Django and Close Browser 16 | Test Teardown Manage Flush 17 | 18 | 19 | *** Keywords *** 20 | 21 | Start Django and open Browser 22 | Start Django 23 | Open Browser ${SERVER} ${BROWSER} 24 | 25 | Stop Django and close browser 26 | Close Browser 27 | Stop Django 28 | 29 | 30 | *** Test Cases *** 31 | 32 | Factory Boy Keyword Should Return Object 33 | ${user}= Factory Boy DjangoLibrary.tests.factories.UserFactory 34 | Dictionary Should Contain Key ${user} username 35 | Dictionary should contain key ${user} password 36 | Dictionary should contain item ${user} username johndoe 37 | Dictionary should contain item ${user} email johndoe@example.com 38 | Dictionary should contain item ${user} is_superuser False 39 | Dictionary should contain item ${user} is_staff False 40 | 41 | Factory Boy Keyword Should Return Primary Key Attribute 42 | ${user}= Factory Boy DjangoLibrary.tests.factories.UserFactory 43 | 44 | Dictionary should contain key ${user} pk 45 | 46 | Factory Boy Keyword Should Override Attributes 47 | ${user}= Factory Boy DjangoLibrary.tests.factories.UserFactory 48 | ... username=janedoe 49 | 50 | Dictionary should contain item ${user} username janedoe 51 | Dictionary should contain item ${user} email janedoe@example.com 52 | 53 | Factory Boy Keyword Should Override Multiple Attributes 54 | ${user}= Factory Boy DjangoLibrary.tests.factories.UserFactory 55 | ... username=janedoe 56 | ... email=jane@doe.com 57 | 58 | Dictionary should contain item ${user} username janedoe 59 | Dictionary should contain item ${user} email jane@doe.com 60 | 61 | Factory Boy Keyword Should Work For Author 62 | ${author}= Factory Boy bookstore.factories.AuthorFactory 63 | Dictionary Should Contain Key ${author} name 64 | Dictionary should contain item ${author} name Noam Chomsky 65 | 66 | Factory Boy Keyword Should Work For Book 67 | ${book}= Factory Boy bookstore.factories.BookFactory 68 | Dictionary Should Contain Key ${book} title 69 | Dictionary should contain item ${book} title Colorless Green Ideas Sleep Furiously 70 | 71 | Factory Boy Keyword Should Get Or Create An Object 72 | # Create two authors with the same name 73 | Factory Boy bookstore.factories.AuthorFactory name=Howard Zinn 74 | Factory Boy bookstore.factories.AuthorFactory name=Howard Zinn 75 | # The author should not be created twice 76 | ${result}= QuerySet bookstore.models.Author name=Howard Zinn 77 | Length should be ${result} 1 78 | 79 | Factory Boy Keyword Should Work With Subfactory 80 | ${book}= Factory Boy bookstore.factories.BookFactory 81 | ... title=A People's History of the United States 82 | ... author__name=Howard Zinn 83 | Dictionary Should Contain Key ${book} title 84 | Dictionary should contain item ${book} title A People's History of the United States 85 | Dictionary Should Contain Key ${book} author 86 | 87 | Factory Boy Keyword Should Work With Subfactory Subfactory 88 | ${book}= Factory Boy bookstore.factories.BookFactory 89 | ... title=A People's History of the United States 90 | ... author__name=Howard Zinn 91 | ... author__university__name=Boston University 92 | Dictionary should contain item ${book} title A People's History of the United States 93 | Dictionary Should Contain Key ${book} author 94 | ${result}= QuerySet bookstore.models.University name=Boston University 95 | Length should be ${result} 1 96 | 97 | Factory Boy Keyword Should Work With Subfactory and Existing Content 98 | ${author}= Factory Boy bookstore.factories.AuthorFactory name=Howard Zinn 99 | ${author_id}= Get From Dictionary ${author} id 100 | ${book}= Factory Boy bookstore.factories.BookFactory 101 | ... title=A People's History of the United States 102 | ... author__pk=${author_id} 103 | Dictionary Should Contain Key ${book} title 104 | Dictionary should contain item ${book} title A People's History of the United States 105 | Dictionary Should Contain Key ${book} author 106 | 107 | Factory Boy Keyword Should Work With Subfactory, Existing Content, and QuerySet Lookup 108 | Factory Boy bookstore.factories.AuthorFactory name=Howard Zinn 109 | ${authors}= QuerySet bookstore.models.Author name=Howard Zinn 110 | ${author}= Get From List ${authors} 0 111 | ${author_id}= Get From Dictionary ${author} id 112 | ${book}= Factory Boy bookstore.factories.BookFactory 113 | ... title=A People's History of the United States 114 | ... author__pk=${author_id} 115 | Dictionary Should Contain Key ${book} title 116 | Dictionary should contain item ${book} title A People's History of the United States 117 | Dictionary Should Contain Key ${book} author 118 | 119 | Factory Boy Keyword Should Work With Subfactory And Reuse Of Existing Content 120 | # Ensure that exactly one Howard Zinn exists 121 | Factory Boy bookstore.factories.AuthorFactory name=Howard Zinn 122 | ${authors}= QuerySet bookstore.models.Author name=Howard Zinn 123 | Length Should Be ${authors} 1 124 | 125 | # Create a Book with author__name=Howard Zinn 126 | ${book}= Factory Boy bookstore.factories.BookFactory 127 | ... title=A People's History of the United States 128 | ... author__name=Howard Zinn 129 | Dictionary Should Contain Key ${book} title 130 | Dictionary should contain item ${book} title A People's History of the United States 131 | Dictionary Should Contain Key ${book} author 132 | 133 | # Ensure that call to the BookFactory did not create another 134 | # entry on the Author table, but reused the existing author 135 | ${authors}= QuerySet bookstore.models.Author name=Howard Zinn 136 | Length Should Be ${authors} 1 137 | 138 | Factory Boy Keyword Should Raise an Exception If Path Does Not Exist 139 | ${expected_error}= catenate SEPARATOR=${SPACE} 140 | ... HTTPError: Factory Boy class "Non.Existing.Path" could not be found 141 | Run Keyword and Expect Error ${expected_error} Factory Boy Non.Existing.Path 142 | 143 | Factory Boy Keyword Should Raise an Exception On Broken Class 144 | ${expected_error}= catenate SEPARATOR=${SPACE} 145 | ... HTTPError: FactoryBoyClass 146 | ... "DjangoLibrary.tests.factories.BrokenFactory" 147 | ... could not be instantiated with args "{}" 148 | Run Keyword and Expect Error ${expected_error} Factory Boy DjangoLibrary.tests.factories.BrokenFactory 149 | 150 | Factory Boy Keyword Should Raise an Exception On Class Without Meta Class 151 | ${expected_error}= catenate SEPARATOR=${SPACE} 152 | ... HTTPError: FactoryBoyClass 153 | ... "DjangoLibrary.tests.factories.BrokenFactoryWithoutMetaClass" 154 | ... could not be instantiated with args "{}" 155 | Run Keyword and Expect Error ${expected_error} Factory Boy DjangoLibrary.tests.factories.BrokenFactoryWithoutMetaClass 156 | 157 | Factory Boy Keyword Should Raise an Exception If Class Does Not Inherit From DjangoModelFactory 158 | ${expected_error}= catenate SEPARATOR=${SPACE} 159 | ... HTTPError: The FactoryBoyClass 160 | ... "DjangoLibrary.tests.factories.BrokenFactoryClassDoesNotInheritFromDjangoModelFactory" 161 | ... instance does not seem to provide a _meta attribute. 162 | ... Please check if the Factory Boy class inherits from DjangoModelFactory 163 | Run Keyword and Expect Error ${expected_error} Factory Boy DjangoLibrary.tests.factories.BrokenFactoryClassDoesNotInheritFromDjangoModelFactory 164 | -------------------------------------------------------------------------------- /DjangoLibrary/tests/test_isolation.robot: -------------------------------------------------------------------------------- 1 | *** Variables *** 2 | 3 | ${SERVER} http://localhost:55001 4 | ${BROWSER} chrome 5 | 6 | 7 | *** Settings *** 8 | 9 | Documentation Testing Test Isolation 10 | Library SeleniumLibrary timeout=10 implicit_wait=0.5 11 | Library DjangoLibrary 127.0.0.1 55001 settings=mysite.robotframework_settings 12 | Library DebugLibrary 13 | Suite Setup Start Django and Open Browser 14 | Suite Teardown Stop Django and Close Browser 15 | Test Teardown Manage Flush 16 | 17 | 18 | *** Keywords *** 19 | 20 | Start Django and open Browser 21 | Start Django 22 | Open Browser ${SERVER} ${BROWSER} 23 | 24 | Stop Django and close browser 25 | Close Browser 26 | Stop Django 27 | 28 | 29 | *** Test Cases *** 30 | 31 | Test One: Create User 32 | Create User test-user-1 test@test.com password is_superuser=True is_staff=True 33 | Autologin as test-user-1 password 34 | Go To ${SERVER}/admin 35 | Wait until page contains Django administration 36 | Page should contain Site administration 37 | 38 | 39 | Test Two: User from Test One should not be present 40 | Autologin as test-user-1 password 41 | Go To ${SERVER}/admin 42 | Wait until page contains Django administration 43 | Page should not contain Site administration 44 | Page should contain Log in 45 | -------------------------------------------------------------------------------- /DjangoLibrary/tests/test_query_set.robot: -------------------------------------------------------------------------------- 1 | *** Variables *** 2 | 3 | ${SERVER} http://localhost:55001 4 | ${BROWSER} chrome 5 | 6 | 7 | *** Settings *** 8 | 9 | Documentation Testing Query Keyword 10 | Library SeleniumLibrary timeout=10 implicit_wait=0 11 | Library DjangoLibrary 127.0.0.1 55001 settings=mysite.robotframework_settings 12 | Library Collections 13 | Library DebugLibrary 14 | Suite Setup Start Django and Open Browser 15 | Suite Teardown Stop Django and Close Browser 16 | Test Teardown Manage Flush 17 | 18 | 19 | *** Keywords *** 20 | 21 | Start Django and open Browser 22 | Start Django 23 | Open Browser ${SERVER} ${BROWSER} 24 | 25 | Stop Django and close browser 26 | Close Browser 27 | Stop Django 28 | 29 | 30 | *** Test Cases *** 31 | 32 | Test query without parameters 33 | Factory Boy DjangoLibrary.tests.factories.UserFactory 34 | ${result}= QuerySet django.contrib.auth.models.User 35 | # Log List ${result} WARN 36 | ${user}= Get From List ${result} 0 37 | Dictionary Should Contain Key ${user} username 38 | Dictionary should contain item ${user} username johndoe 39 | Dictionary should contain item ${user} email johndoe@example.com 40 | Dictionary should contain item ${user} is_superuser False 41 | Dictionary should contain item ${user} is_staff False 42 | 43 | Test query with single parameter 44 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=janedoe 45 | ${result}= QuerySet django.contrib.auth.models.User username=janedoe 46 | # Log List ${result} WARN 47 | ${user}= Get From List ${result} 0 48 | Dictionary Should Contain Key ${user} username 49 | Dictionary should contain item ${user} username janedoe 50 | 51 | Test query with single parameter returns none 52 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=janedoe 53 | ${result}= QuerySet django.contrib.auth.models.User username=johndoe 54 | # Log List ${result} WARN 55 | Length should be ${result} 0 56 | 57 | Test query with Book 58 | Factory Boy bookstore.factories.BookFactory 59 | ${result}= QuerySet bookstore.models.Book 60 | # Log List ${result} WARN 61 | ${book}= Get From List ${result} 0 62 | Dictionary Should contain key ${book} title 63 | Dictionary should contain item ${book} title Colorless Green Ideas Sleep Furiously 64 | 65 | Test query with limit 66 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user1 67 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user2 68 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user3 69 | ${result}= QuerySet django.contrib.auth.models.User limit=1 70 | # Log List ${result} WARN 71 | Length should be ${result} 1 72 | ${user}= Get From List ${result} 0 73 | Dictionary Should Contain Key ${user} username 74 | Dictionary should contain item ${user} username user1 75 | 76 | Test query with offset and limit 77 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user1 78 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user2 79 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user3 80 | ${result}= QuerySet django.contrib.auth.models.User offset=1 limit=2 81 | # Log List ${result} WARN 82 | Length should be ${result} 1 83 | ${user}= Get From List ${result} 0 84 | Dictionary Should Contain Key ${user} username 85 | Dictionary should contain item ${user} username user2 86 | 87 | Test query with offset and limit (multiple results) 88 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user1 89 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user2 90 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user3 91 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user4 92 | Factory Boy DjangoLibrary.tests.factories.UserFactory username=user5 93 | ${result}= QuerySet django.contrib.auth.models.User offset=2 limit=4 94 | # Log List ${result} WARN 95 | Length should be ${result} 2 96 | ${user}= Get From List ${result} 0 97 | Dictionary Should Contain Key ${user} username 98 | Dictionary should contain item ${user} username user3 99 | ${user}= Get From List ${result} 1 100 | Dictionary Should Contain Key ${user} username 101 | Dictionary should contain item ${user} username user4 102 | -------------------------------------------------------------------------------- /DjangoLibrary/tests/test_selenium_bug_4198.robot.disabled: -------------------------------------------------------------------------------- 1 | *** Variables *** 2 | 3 | ${SERVER} http://localhost:55001 4 | ${BROWSER} chrome 5 | 6 | 7 | *** Settings *** 8 | 9 | Documentation Django Robot Tests 10 | Library SeleniumLibrary timeout=10 implicit_wait=0 11 | Library DjangoLibrary 127.0.0.1 55001 path=mysite/mysite manage=mysite/manage.py settings=mysite.settings db=mysite/db.sqlite3 12 | Suite Setup Start Django and Open Browser 13 | Suite Teardown Stop Django and Close Browser 14 | 15 | 16 | *** Test Cases *** 17 | 18 | Scenario: Type ü into input field 19 | Go To ${SERVER}/admin 20 | Wait until page contains Django administration 21 | Page Should Contain Django administration 22 | Page Should Contain Element name=username 23 | Page Should Contain Element name=password 24 | Input Text name=username München 25 | Page should contain element xpath=//input[@name='username' and @value='München'] 26 | 27 | *** Keywords *** 28 | 29 | Start Django and open Browser 30 | Start Django 31 | Open Browser ${SERVER} ${BROWSER} 32 | 33 | Stop Django and close browser 34 | Close Browser 35 | Stop Django 36 | 37 | Pause 38 | Import library Dialogs 39 | Pause execution 40 | 41 | Logout 42 | Go To ${SERVER}/admin/logout 43 | Wait until page contains Logged out 44 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include main_directory * 2 | recursive-include docs * 3 | include * 4 | global-exclude *.pyc 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | CURRENT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 3 | DJANGO_VERSION:=1.11.5 4 | 5 | all: clean build 6 | 7 | clean: 8 | @echo "Clean" 9 | rm -rf bin include lib 10 | 11 | build: 12 | @echo "Build" 13 | virtualenv -p python3 . 14 | bin/pip install -r requirements.txt 15 | bin/python setup.py develop 16 | bin/pip install . --no-dependencies 17 | bin/pip install -q flake8 18 | bin/pip install -q psycopg2 19 | bin/pip install Django==${DJANGO_VERSION} 20 | 21 | test: 22 | @echo "Run Tests" 23 | bin/robot DjangoLibrary 24 | 25 | build-docs: 26 | @echo "Build Keyword Documentation" 27 | bin/python -m robot.libdoc DjangoLibrary docs/index.html 28 | 29 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | A robot framework library for Django. 3 | ============================================================================== 4 | 5 | .. image:: https://travis-ci.org/kitconcept/robotframework-djangolibrary.svg?branch=master 6 | :target: https://travis-ci.org/kitconcept/robotframework-djangolibrary 7 | 8 | .. image:: https://img.shields.io/pypi/status/robotframework-djangolibrary.svg 9 | :target: https://pypi.python.org/pypi/robotframework-djangolibrary/ 10 | :alt: Egg Status 11 | 12 | .. image:: https://img.shields.io/pypi/v/robotframework-djangolibrary.svg 13 | :target: https://pypi.python.org/pypi/robotframework-djangolibrary/ 14 | :alt: Latest Version 15 | 16 | .. image:: https://img.shields.io/pypi/l/robotframework-djangolibrary.svg 17 | :target: https://pypi.python.org/pypi/robotframework-djangolibrary/ 18 | :alt: License 19 | 20 | | 21 | 22 | .. image:: https://raw.githubusercontent.com/kitconcept/robotframework-djangolibrary/master/kitconcept.png 23 | :alt: kitconcept 24 | :target: https://kitconcept.com/ 25 | 26 | 27 | Introduction 28 | ------------ 29 | 30 | DjangoLibrary is a web testing library to test Django with Robot Framework. 31 | It uses SeleniumLibrary to run tests against a real browser instance. 32 | 33 | The library will automatically start and stop your Django instance while running the tests. 34 | It also comes with serveral autologin keywords that allow you to login different users during your tests, without the need to actually access the login page. 35 | 36 | DjangoLibrary is currently tested against Django 1.8.x, 1.9.x, 1.11.x with SQLite and Postgres on Python 2.7 and 3.6. 37 | 38 | 39 | Documentation 40 | ------------- 41 | 42 | `Robot Framework Django Library Keyword Documentation`_ 43 | 44 | 45 | Installation 46 | ------------ 47 | 48 | Install robotframework-djangolibrary with pip:: 49 | 50 | $ pip install robotframework-djangolibrary 51 | 52 | In order to be able to use DjangoLibrary's `Autologin`, `FactoryBoy`, or 53 | `QuerySet` keywords you have to add the corresponding middleware classes to 54 | your MIDDLEWARE_CLASSES in yoursettings.py:: 55 | 56 | MIDDLEWARE_CLASSES = ( 57 | ... 58 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 59 | 'DjangoLibrary.middleware.AutologinAuthenticationMiddleware', 60 | 'DjangoLibrary.middleware.FactoryBoyMiddleware', 61 | 'DjangoLibrary.middleware.QuerySetMiddleware', 62 | ) 63 | 64 | .. DANGER:: 65 | Make sure that you add those middlewares only to your test setup and 66 | NEVER to your deployment! The Autologin middleware just checks for a 67 | 'autologin' cookie and then authenticates and login ANY user. 68 | 69 | 70 | First Robot Test 71 | ---------------- 72 | 73 | In order to write your first robot test, make sure that you include SeleniumLibrary and DjangoLibrary. Create a test.robot file with the 74 | following content:: 75 | 76 | *** Variables *** 77 | 78 | ${HOSTNAME} 127.0.0.1 79 | ${PORT} 55001 80 | ${SERVER} http://${HOSTNAME}:${PORT}/ 81 | ${BROWSER} firefox 82 | 83 | 84 | *** Settings *** 85 | 86 | Documentation Django Robot Tests 87 | Library SeleniumLibrary timeout=10 implicit_wait=0 88 | Library DjangoLibrary ${HOSTNAME} ${PORT} path=mysite/mysite manage=mysite/manage.py settings=mysite.settings 89 | Suite Setup Start Django and open Browser 90 | Suite Teardown Stop Django and close Browser 91 | 92 | 93 | *** Keywords *** 94 | 95 | Start Django and open Browser 96 | Start Django 97 | Open Browser ${SERVER} ${BROWSER} 98 | 99 | Stop Django and close browser 100 | Close Browser 101 | Stop Django 102 | 103 | 104 | *** Test Cases *** 105 | 106 | Scenario: As a visitor I can visit the django default page 107 | Go To ${SERVER} 108 | Wait until page contains element id=explanation 109 | Page Should Contain It worked! 110 | Page Should Contain Congratulations on your first Django-powered page. 111 | 112 | 113 | License 114 | ------- 115 | 116 | Copyright kitconcept GmbH. 117 | 118 | Distributed under the terms of the Apache License 2.0, robotframework-djangolibrary is free and Open Source software. 119 | 120 | 121 | Contribute 122 | ---------- 123 | 124 | - `Source code at Github `_ 125 | - `Issue tracker at Github `_ 126 | 127 | 128 | Support 129 | ------- 130 | 131 | If you are having issues, `please let us know `_. If you require professional support feel free to contact us at `info@kitconcept.com. `_ 132 | 133 | 134 | Run Tests 135 | --------- 136 | 137 | Then you can run the test with robot:: 138 | 139 | $ robot test.robot 140 | 141 | The output should look like this:: 142 | 143 | ============================================================================== 144 | Test :: Django Robot Tests 145 | ============================================================================== 146 | Scenario: As a visitor I can visit the django default page | PASS | 147 | ------------------------------------------------------------------------------ 148 | Test :: Django Robot Tests | PASS | 149 | 1 critical test, 1 passed, 0 failed 150 | 1 test total, 1 passed, 0 failed 151 | ============================================================================== 152 | Output: /home/timo/workspace/prounix/robotframework-djangolibrary/output.xml 153 | Log: /home/timo/workspace/prounix/robotframework-djangolibrary/log.html 154 | Report: /home/timo/workspace/prounix/robotframework-djangolibrary/report.html 155 | 156 | 157 | Test Isolation 158 | -------------- 159 | 160 | robotframework-djangolibrary does not provide isolation between tests by 161 | default. This means if you add an object to the database in a test, this 162 | object will be present in the next test as well. You need to cleanup 163 | yourself in order to have a proper isolation between the tests. You can use 164 | the robotframework "Test Teardown" call to call the "Clear DB" keyword after 165 | each test:: 166 | 167 | *** Settings *** 168 | 169 | Library SeleniumLibrary timeout=10 implicit_wait=0 170 | Library DjangoLibrary ${HOSTNAME} ${PORT} path=mysite/mysite manage=mysite/manage.py settings=mysite.settings db=mysite/db.sqlite3 171 | Suite Setup Start Django and open Browser 172 | Suite Teardown Stop Django and close Browser 173 | Test Teardown Clear DB 174 | 175 | 176 | Development 177 | ----------- 178 | 179 | Checkout repository from github:: 180 | 181 | $ git clone https://github.com/kitconcept/robotframework-djangolibrary.git 182 | 183 | Create a virtual Python environment:: 184 | 185 | $ cd robotframework-djangolibrary/ 186 | $ virtualenv .py27 187 | $ source .py27/bin/activate 188 | 189 | Install robotframework-djangolibrary in development mode:: 190 | 191 | $ python setup.py develop 192 | 193 | Install the requirements:: 194 | 195 | $ pip install -r requirements.txt 196 | 197 | Run Unit/Integration-Tests:: 198 | 199 | $ py.test mysite/ 200 | 201 | Run Acceptance Tests:: 202 | 203 | $ robot DjangoLibrary/tests/ 204 | 205 | .. _`Robot Framework Django Library Keyword Documentation`: https://kitconcept.github.io/robotframework-djangolibrary/ 206 | -------------------------------------------------------------------------------- /docs/DjangoLibraryDocs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 160 | 173 | 196 | 247 | 441 | 445 | 457 | 470 | 473 | 474 | 475 | 476 | 477 |
478 |

Opening library documentation failed

479 |
    480 |
  • Verify that you have JavaScript enabled in your browser.
  • 481 |
  • Make sure you are using a modern enough browser. Firefox 3.5, IE 8, or equivalent is required, newer browsers are recommended.
  • 482 |
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • 483 |
484 |
485 | 486 | 490 | 491 | 690 | 691 | 736 | 737 | 756 | 757 | 768 | 769 | 780 | 781 | 797 | 798 | 820 | 821 | 822 | 830 | 831 | 832 | 833 | -------------------------------------------------------------------------------- /kitconcept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitconcept/robotframework-djangolibrary/fda7454482b889a90b727adf7444929c3b597fa3/kitconcept.png -------------------------------------------------------------------------------- /mysite/bookstore/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitconcept/robotframework-djangolibrary/fda7454482b889a90b727adf7444929c3b597fa3/mysite/bookstore/__init__.py -------------------------------------------------------------------------------- /mysite/bookstore/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /mysite/bookstore/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class BookstoreConfig(AppConfig): 7 | name = 'bookstore' 8 | -------------------------------------------------------------------------------- /mysite/bookstore/factories.py: -------------------------------------------------------------------------------- 1 | from factory import DjangoModelFactory 2 | from .models import Author 3 | from .models import Book 4 | from .models import University 5 | import factory 6 | 7 | 8 | class UniversityFactory(DjangoModelFactory): 9 | class Meta: 10 | model = University 11 | django_get_or_create = ('name',) 12 | 13 | name = 'MIT' 14 | 15 | 16 | class AuthorFactory(DjangoModelFactory): 17 | class Meta: 18 | model = Author 19 | django_get_or_create = ('name',) 20 | 21 | name = 'Noam Chomsky' 22 | university = factory.SubFactory(UniversityFactory) 23 | 24 | 25 | class BookFactory(DjangoModelFactory): 26 | class Meta: 27 | model = Book 28 | django_get_or_create = ('title',) 29 | 30 | title = 'Colorless Green Ideas Sleep Furiously' 31 | author = factory.SubFactory(AuthorFactory) 32 | -------------------------------------------------------------------------------- /mysite/bookstore/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-04-15 13:14 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Author', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('name', models.CharField(max_length=255, verbose_name='Author Name')), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name='Book', 26 | fields=[ 27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 28 | ('title', models.TextField(verbose_name='Book Title')), 29 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookstore.Author')), 30 | ], 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /mysite/bookstore/migrations/0002_auto_20160701_0830.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-07-01 08:30 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('bookstore', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='author', 17 | name='id', 18 | field=models.AutoField(primary_key=True, serialize=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /mysite/bookstore/migrations/0003_auto_20160701_0845.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-07-01 08:45 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('bookstore', '0002_auto_20160701_0830'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='book', 17 | name='id', 18 | field=models.AutoField(primary_key=True, serialize=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /mysite/bookstore/migrations/0004_auto_20160701_1711.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-07-01 17:11 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('bookstore', '0003_auto_20160701_0845'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='author', 17 | name='id', 18 | field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 19 | ), 20 | migrations.AlterField( 21 | model_name='book', 22 | name='id', 23 | field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /mysite/bookstore/migrations/0005_auto_20160701_1859.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-07-01 18:59 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('bookstore', '0004_auto_20160701_1711'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='University', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('name', models.CharField(max_length=255, verbose_name='University Name')), 21 | ], 22 | ), 23 | migrations.AddField( 24 | model_name='author', 25 | name='university', 26 | field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='bookstore.University'), 27 | preserve_default=False, 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /mysite/bookstore/migrations/0006_auto_20160701_1900.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-07-01 19:00 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('bookstore', '0005_auto_20160701_1859'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='author', 18 | name='university', 19 | field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='bookstore.University'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /mysite/bookstore/migrations/0007_auto_20160701_1900.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-07-01 19:00 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('bookstore', '0006_auto_20160701_1900'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='book', 18 | name='author', 19 | field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='bookstore.Author'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /mysite/bookstore/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitconcept/robotframework-djangolibrary/fda7454482b889a90b727adf7444929c3b597fa3/mysite/bookstore/migrations/__init__.py -------------------------------------------------------------------------------- /mysite/bookstore/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import models 3 | 4 | 5 | class University(models.Model): 6 | 7 | name = models.CharField( 8 | u'University Name', 9 | max_length=255 10 | ) 11 | 12 | 13 | class Author(models.Model): 14 | 15 | name = models.CharField( 16 | u'Author Name', 17 | max_length=255 18 | ) 19 | 20 | university = models.ForeignKey( 21 | University, 22 | default=None 23 | ) 24 | 25 | 26 | class Book(models.Model): 27 | 28 | title = models.TextField( 29 | u'Book Title', 30 | ) 31 | 32 | author = models.ForeignKey( 33 | Author, 34 | default=None 35 | ) 36 | -------------------------------------------------------------------------------- /mysite/bookstore/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitconcept/robotframework-djangolibrary/fda7454482b889a90b727adf7444929c3b597fa3/mysite/bookstore/tests/__init__.py -------------------------------------------------------------------------------- /mysite/bookstore/tests/test_factory.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from ..models import Author 3 | from ..models import Book 4 | from ..factories import AuthorFactory 5 | from ..factories import BookFactory 6 | 7 | 8 | class TestFactoryBoy(TestCase): 9 | 10 | def test_author_factory(self): 11 | author = AuthorFactory() 12 | author.name = 'Noam Chomsky' 13 | self.assertTrue(isinstance(author, Author)) 14 | 15 | def test_book_factory(self): 16 | book = BookFactory() 17 | book.name = 'Colorless Green Ideas Sleep Furiously' 18 | self.assertTrue(isinstance(book, Book)) 19 | -------------------------------------------------------------------------------- /mysite/bookstore/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /mysite/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /mysite/mysite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitconcept/robotframework-djangolibrary/fda7454482b889a90b727adf7444929c3b597fa3/mysite/mysite/__init__.py -------------------------------------------------------------------------------- /mysite/mysite/robotframework_settings.py: -------------------------------------------------------------------------------- 1 | from .settings import * 2 | 3 | DATABASES['default']['NAME'] = 'robotframework' 4 | -------------------------------------------------------------------------------- /mysite/mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysite project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '@7ul7n=fq((22lon1&g%fvv+lm9=fgjsn2g&omqs$=%x6@2_-_' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'bookstore.apps.BookstoreConfig', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | ] 42 | 43 | MIDDLEWARE_CLASSES = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 50 | 'DjangoLibrary.middleware.AutologinAuthenticationMiddleware', 51 | 'DjangoLibrary.middleware.FactoryBoyMiddleware', 52 | 'DjangoLibrary.middleware.QuerySetMiddleware', 53 | 'django.contrib.messages.middleware.MessageMiddleware', 54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 55 | ] 56 | 57 | ROOT_URLCONF = 'mysite.urls' 58 | 59 | TEMPLATES = [ 60 | { 61 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 62 | 'DIRS': [], 63 | 'APP_DIRS': True, 64 | 'OPTIONS': { 65 | 'context_processors': [ 66 | 'django.template.context_processors.debug', 67 | 'django.template.context_processors.request', 68 | 'django.contrib.auth.context_processors.auth', 69 | 'django.contrib.messages.context_processors.messages', 70 | ], 71 | }, 72 | }, 73 | ] 74 | 75 | WSGI_APPLICATION = 'mysite.wsgi.application' 76 | 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 80 | 81 | DATABASES = { 82 | 'default': { 83 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 84 | 'NAME': 'travis_ci_test', 85 | 'USER': 'postgres', 86 | 'PASSWORD': '', 87 | 'HOST': 'localhost', 88 | 'PORT': '', 89 | } 90 | } 91 | 92 | if 'TRAVIS' in os.environ: 93 | if os.environ.get('DB') == 'SQLite': 94 | DATABASES = { 95 | 'default': { 96 | 'ENGINE': 'django.db.backends.sqlite3', 97 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 98 | } 99 | } 100 | elif os.environ.get('DB') == 'Postgres': 101 | DATABASES = { 102 | 'default': { 103 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 104 | 'NAME': 'travis_ci_test', 105 | 'USER': 'postgres', 106 | 'PASSWORD': '', 107 | 'HOST': 'localhost', 108 | 'PORT': '', 109 | } 110 | } 111 | 112 | 113 | # Password validation 114 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 115 | 116 | AUTH_PASSWORD_VALIDATORS = [ 117 | { 118 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', # noqa 119 | }, 120 | { 121 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', # noqa 122 | }, 123 | { 124 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', # noqa 125 | }, 126 | { 127 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', # noqa 128 | }, 129 | ] 130 | 131 | 132 | # Internationalization 133 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 134 | 135 | LANGUAGE_CODE = 'en-us' 136 | 137 | TIME_ZONE = 'UTC' 138 | 139 | USE_I18N = True 140 | 141 | USE_L10N = True 142 | 143 | USE_TZ = True 144 | 145 | 146 | # Static files (CSS, JavaScript, Images) 147 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 148 | 149 | STATIC_URL = '/static/' 150 | -------------------------------------------------------------------------------- /mysite/mysite/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kitconcept/robotframework-djangolibrary/fda7454482b889a90b727adf7444929c3b597fa3/mysite/mysite/tests/__init__.py -------------------------------------------------------------------------------- /mysite/mysite/tests/factories.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.template.defaultfilters import slugify 3 | from factory import DjangoModelFactory, lazy_attribute 4 | from random import randint 5 | 6 | 7 | class UserFactory(DjangoModelFactory): 8 | class Meta: 9 | model = 'auth.User' 10 | django_get_or_create = ('username',) 11 | 12 | first_name = 'John' 13 | last_name = 'Doe' 14 | username = lazy_attribute( 15 | lambda o: slugify(o.first_name + '.' + o.last_name) 16 | ) 17 | email = lazy_attribute(lambda o: o.username + "@example.com") 18 | 19 | @lazy_attribute 20 | def date_joined(self): 21 | return datetime.datetime.now() - datetime.timedelta( 22 | days=randint(5, 50) 23 | ) 24 | last_login = lazy_attribute( 25 | lambda o: o.date_joined + datetime.timedelta(days=4) 26 | ) 27 | -------------------------------------------------------------------------------- /mysite/mysite/tests/test_factory_boy.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.test import TestCase 3 | from mysite.tests.factories import UserFactory 4 | import datetime 5 | 6 | 7 | class TestFactoryBoy(TestCase): 8 | 9 | def test_user_factory(self): 10 | user = UserFactory() 11 | self.assertTrue(isinstance(user, User)) 12 | self.assertEqual('John', user.first_name) 13 | self.assertEqual('Doe', user.last_name) 14 | self.assertEqual('johndoe', user.username) 15 | self.assertEqual('johndoe@example.com', user.email) 16 | self.assertTrue(isinstance(user.date_joined, datetime.datetime)) 17 | -------------------------------------------------------------------------------- /mysite/mysite/tests/test_middleware.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.test import TestCase 3 | from DjangoLibrary.middleware import FactoryBoyMiddleware 4 | from mock import Mock 5 | 6 | import json 7 | 8 | 9 | class TestFactoryBoyMiddleware(TestCase): 10 | 11 | def setUp(self): 12 | self.middleware = FactoryBoyMiddleware() 13 | self.request = Mock() 14 | self.request.session = {} 15 | 16 | def test_process_request_creates_object(self): 17 | self.request.configure_mock( 18 | **{ 19 | 'GET': { 20 | 'FACTORY_BOY_MODEL_PATH': 'mysite.tests.factories.UserFactory', # noqa 21 | 'FACTORY_BOY_ARGS': '' 22 | } 23 | } 24 | ) 25 | 26 | response = self.middleware.process_request(self.request) 27 | 28 | self.assertEqual(201, response.status_code) 29 | self.assertEqual( 30 | 'johndoe', 31 | json.loads(response.content.decode('utf-8')).get('username') 32 | ) 33 | self.assertEqual(1, len(User.objects.values())) 34 | self.assertEqual('johndoe', User.objects.values()[0]['username']) 35 | -------------------------------------------------------------------------------- /mysite/mysite/tests/test_middleware_query.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.test import TestCase 3 | from DjangoLibrary.middleware import QuerySetMiddleware 4 | from mock import Mock 5 | 6 | import json 7 | 8 | 9 | class TestQuerySetMiddleware(TestCase): 10 | 11 | def setUp(self): 12 | self.middleware = QuerySetMiddleware() 13 | self.request = Mock() 14 | self.request.session = {} 15 | 16 | def test_query(self): 17 | self.request.configure_mock( 18 | **{ 19 | 'GET': { 20 | 'MODEL_PATH': 'django.contrib.auth.models.User', 21 | 'ARGS': '' 22 | } 23 | } 24 | ) 25 | 26 | john = User(username='john', email='john@doe.com') 27 | john.save() 28 | response = self.middleware.process_request(self.request) 29 | self.assertEqual(200, response.status_code) 30 | self.assertEqual( 31 | 'john', 32 | json.loads(response.content.decode('utf-8'))[0].get('username') 33 | ) 34 | self.assertEqual( 35 | 'john@doe.com', 36 | json.loads(response.content.decode('utf-8'))[0].get('email') 37 | ) 38 | 39 | def test_empty_query(self): 40 | self.request.configure_mock( 41 | **{ 42 | 'GET': { 43 | 'MODEL_PATH': 'django.contrib.auth.models.User', 44 | 'QUERY_ARGS': '{"username": "jane"}' 45 | } 46 | } 47 | ) 48 | 49 | john = User(username='john', email='john@doe.com') 50 | john.save() 51 | response = self.middleware.process_request(self.request) 52 | self.assertEqual(200, response.status_code) 53 | self.assertEqual( 54 | 0, 55 | len(json.loads(response.content.decode('utf-8'))), 56 | 'Query result should be empty when querying for username "jane"' 57 | ) 58 | -------------------------------------------------------------------------------- /mysite/mysite/urls.py: -------------------------------------------------------------------------------- 1 | """mysite URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^admin/', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /mysite/mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE=mysite.settings 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | robotframework==3.1.1 2 | robotframework-seleniumlibrary==3.3.1 3 | robotframework-debuglibrary==1.1.4 4 | Selenium==3.141.0 5 | factory_boy==2.11.1 6 | pytest==4.4.0 7 | pytest-django==3.4.8 8 | psycopg2==2.8.1 9 | mock==2.0.0 10 | zest.releaser==6.18.1 11 | twine==1.12.1 12 | requests==2.21.0 13 | urllib3==1.24.1 14 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | version = '3.1.1-dev.0' 4 | 5 | long_description = ( 6 | open('README.rst').read() + 7 | '\n' + 8 | '\n' + 9 | open('CHANGES.rst').read() + 10 | '\n') 11 | 12 | setup( 13 | name='robotframework-djangolibrary', 14 | version=version, 15 | description="A Robot Framework library for Django.", 16 | long_description=long_description, 17 | classifiers=[ 18 | 'Development Status :: 5 - Production/Stable', 19 | 'License :: OSI Approved :: Apache Software License', 20 | 'Environment :: Web Environment', 21 | 'Framework :: Robot Framework', 22 | 'Framework :: Django', 23 | 'Framework :: Django :: 1.5', 24 | 'Framework :: Django :: 1.6', 25 | 'Framework :: Django :: 1.7', 26 | 'Framework :: Django :: 1.8', 27 | 'Framework :: Django :: 1.9', 28 | 'Framework :: Django :: 1.10', 29 | 'Framework :: Django :: 1.11', 30 | 'Programming Language :: Python :: 2.7', 31 | 'Programming Language :: Python :: 3.5', 32 | 'Programming Language :: Python :: 3.6', 33 | 'Programming Language :: Python :: 3.7', 34 | 'Programming Language :: Python :: Implementation :: CPython', 35 | ], 36 | # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers 37 | keywords='robotframework django test', 38 | author='kitconcept GmbH | Timo Stollenwerk', 39 | author_email='stollenwerk@kitconcept.com', 40 | url='https://kitconcept.com', 41 | license='Apache License 2.0', 42 | packages=find_packages( 43 | exclude=['ez_setup', 'examples', 'tests'] 44 | ), 45 | include_package_data=True, 46 | zip_safe=False, 47 | install_requires=[ 48 | 'Django', 49 | 'factory_boy', 50 | 'requests', 51 | 'robotframework', 52 | 'robotframework-seleniumlibrary', 53 | 'robotframework-debuglibrary', 54 | 'six', 55 | ], 56 | entry_points=""" 57 | # -*- Entry points: -*- 58 | """, 59 | ) 60 | --------------------------------------------------------------------------------