I am trying to run functional tests using Selenium for a Django project. On both Firefox and Chrome I am getting a weird Foreign Key error when I try to test that a superuser can change a normal user's status to staff (I assume this is all verified via Django's internal testing, but thought it would be good practice to include it in my app's testing since my user scenarios depend on the functionality). It almost looks like Django doesn't like Selenium saving anything to the database? This one error trickles down to my other tests, too, so it seems like something breaks behind the scenes--like Selenium loses its database connection. Does anyone know why this happens?? I'm willing to dump the test in favor of assuming that the functionality works, but would love to fix this and use the test.
One possible symptom is that in Chrome, between between the outputs of "checked the box" and "clicked the save button", I get a broken pipe (does not happen in Firefox).
It seems to break around here, whenever Selenium clicks the 'save' button:
# Form looks properly rendered, now click the 'Staff status'
# checkbox and submit it
isStaffCheckbox = self.browser.find_element_by_id('id_is_staff')
isStaffCheckbox.click()
print 'checked the box'
# Save the form
saveBtn = self.browser.find_element_by_css_selector('input[value="Save"]')
saveBtn.click()
print 'clicked the save button'
The error message I get is:
Traceback (most recent call last):
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/test/testcases.py", line 268, in __call__
self._post_teardown()
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/test/testcases.py", line 533, in _post_teardown
self._fixture_teardown()
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/test/testcases.py", line 553, in _fixture_teardown
skip_validation=True, reset_sequences=False)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/core/management/__init__.py", line 161, in call_command
return klass.execute(*args, **defaults)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/core/management/base.py", line 255, in execute
output = self.handle(*args, **options)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/core/management/base.py", line 385, in handle
return self.handle_noargs(**options)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/core/management/commands/flush.py", line 82, in handle_noargs
emit_post_sync_signal(set(all_models), verbosity, interactive, db)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/core/management/sql.py", line 195, in emit_post_sync_signal
interactive=interactive, db=db)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/dispatch/dispatcher.py", line 170, in send
response = receiver(signal=self, sender=sender, **named)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/contrib/auth/management/__init__.py", line 96, in create_permissions
auth_app.Permission.objects.using(db).bulk_create(perms)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/db/models/query.py", line 444, in bulk_create
self._batched_insert(objs_without_pk, fields, batch_size)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/db/models/query.py", line 902, in _batched_insert
using=self.db)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/db/models/manager.py", line 215, in _insert
return insert_query(self.model, objs, fields, **kwargs)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/db/models/query.py", line 1661, in insert_query
return query.get_compiler(using=using).execute_sql(return_id)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 937, in execute_sql
cursor.execute(sql, params)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/db/backends/mysql/base.py", line 122, in execute
six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/django/db/backends/mysql/base.py", line 120, in execute
return self.cursor.execute(query, args)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/MySQL_python-1.2.4b4-py2.7-macosx-10.8-intel.egg/MySQLdb/cursors.py", line 202, in execute
self.errorhandler(self, exc, value)
File "~/virtual_environments/VideoSearch/lib/python2.7/site-packages/MySQL_python-1.2.4b4-py2.7-macosx-10.8-intel.egg/MySQLdb/connections.py", line 36, in defaulterrorhandler
raise errorclass, errorvalue
IntegrityError: (1452, 'Cannot add or update a child row: a foreign key constraint fails (`test_videos2002`.`auth_permission`, CONSTRAINT `content_type_id_refs_id_d043b34a` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`))')
=====
Updated with code
Also note that test_admin_can_make_a_user_staff causes my other tests to Error out with the same error--but they are okay when I take out the "save" command from that test.
I guess two things I really don't understand are: 1) Why this happens with the built-in Django admin view (thought it should just work), and 2) Why one test error cascades through to my other tests? I thought they were independent.
Thanks for looking at this!
From my functional_tests.test.py (borrowed from https://github.com/lincolnloop/django-selenium-intro/tree/master/selenium_intro):
from django.test import LiveServerTestCase
class SeleniumTestCase(LiveServerTestCase):
"""
A base test case for selenium, providing hepler methods for generating
clients and logging in profiles.
"""
def open(self, url):
self.browser.get("%s%s" % (self.live_server_url, url))
From my admin_django.py (set of test cases)
from functional_tests.test import SeleniumTestCase
from selenium.webdriver.common.keys import Keys
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.test import LiveServerTestCase
from django.conf import settings
from selenium import webdriver
class AdminDjango(SeleniumTestCase):
def setUp(self):
User.objects.create_superuser(username='vcb',
password='rock5!',
email='me#name.edu')
User.objects.create_user(username='teaching',
password='assistant',
email='ta#name.edu')
# self.browser = webdriver.Chrome(settings.SELENIUM_WEBDRIVER)
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3)
self.browser.set_page_load_timeout(10)
def tearDown(self):
self.browser.quit()
def check_for_links(self, link_text):
"""
Helper function to check links on a page for certain text
"""
links = self.browser.find_elements_by_tag_name('a')
self.assertTrue(link_text, [link.text for link in links])
def admin_logs_in(self):
"""
Helper function that logs the admin user into the page
"""
username_field = self.browser.find_element_by_name('username')
username_field.send_keys('vcb')
password_field = self.browser.find_element_by_name('password')
password_field.send_keys('rock5!')
password_field.send_keys(Keys.RETURN)
def admin_log_in_complete(self):
"""
Includes navigation to the admin page
"""
self.open('/admin/')
self.admin_logs_in()
def test_admin_can_login(self):
"""
Admin user can log into the Django admin interface
"""
self.open('/admin/')
body = self.browser.find_element_by_tag_name('body')
self.assertIn('VCB Administration', body.text)
self.admin_logs_in()
# her username and password are accepted, and she is taken to
# the Site Administration page
body = self.browser.find_element_by_tag_name('body')
self.assertIn('Site administration', body.text)
def test_admin_page_renders_properly(self):
"""
The admin page should have at least two fields:
- Users
- Classes
Admins may have to add staff status to users, and they may have to
adjust the information for a class
"""
self.admin_log_in_complete()
self.check_for_links('Users')
self.check_for_links('Groups')
self.check_for_links('Classess')
def test_admin_can_make_a_user_staff(self):
"""
Admin users can add staff status to existing users
"""
self.admin_log_in_complete()
pageLinks = self.browser.find_elements_by_tag_name('a')
for link in pageLinks:
if link.text == 'Users':
userLink = link
userLink.click()
headers = self.browser.find_elements_by_tag_name('h1')
self.assertTrue('Select user to change',
[header.text for header in headers])
users = self.browser.find_elements_by_xpath('//table[#id="result_list"]/tbody/tr/th/a')
self.fail('Finish writing the test!')
# rowCount = 1
# for user in users:
# xpath = '//table[#id="result_list"]/tbody/tr[' + str(rowCount) + ']/td[5]/img'
# # check first that this user is not a staff
# staffIcon = self.browser.find_element_by_xpath(xpath)
# isStaff = staffIcon.get_attribute('alt')
#
# if isStaff == 'false':
# user.click()
# userHeaders = self.browser.find_elements_by_tag_name('h1')
# self.assertTrue('Change user',
# [userHeader.text for userHeader in userHeaders])
#
# # Are the right fields present in the user's form?
# formHeaders = self.browser.find_elements_by_tag_name('h2')
# self.assertTrue('Personal info',
# [formHeader.text for formHeader in formHeaders])
# self.assertTrue('Permissions',
# [formHeader.text for formHeader in formHeaders])
# self.assertTrue('Important dates',
# [formHeader.text for formHeader in formHeaders])
#
# # Form looks properly rendered, now click the 'Staff status'
# # checkbox and submit it
# isStaffCheckbox = self.browser.find_element_by_id('id_is_staff')
# isStaffCheckbox.click()
# print 'checked the box'
# # Save the form
# saveBtn = self.browser.find_element_by_css_selector('input[value="Save"]')
# saveBtn.click()
# print 'clicked the save button'
# # Returns you to the admin page
# messageBox = self.browser.find_element_by_class_name('info')
# self.assertIn('successfully', messageBox.text)
#
# # Check that staff status changed
# staffIcon = self.browser.find_element_by_xpath('//table[#id="result_list"]/tbody/tr[' + str(rowCount) + ']/td[5]/img')
# isStaff = staffIcon.get_attribute('alt')
# self.assertTrue(isStaff)
# print 'should now be staff'
# rowCount += 1
def test_admin_can_change_a_class_obj_bank_id(self):
"""
Admin users can change a class's objective bank id
"""
self.fail('Finish writing the test!')
def test_logging_out_redirects_to_login_page(self):
"""
Logging out of the admin page should redirect to the main page
"""
self.admin_log_in_complete()
logOut = self.browser.find_element_by_link_text('Log out')
logOut.click()
body = self.browser.find_element_by_tag_name('body')
self.assertIn('VCB Administration', body.text)
As I mentioned in the comments, this seems to be a documented bug in Django. Here and here are the official bug reports. As reported in the second link, one workaround is to swap the order of django.contrib.auth and django.contrib.contenttypes in INSTALLED_APPS, as such:
What originally is this:
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'testapp'
)
Should become:
INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'testapp'
)
Related
I've got the following custom action in my view:
class OrderAPIViewSet(viewsets.ViewSet):
def create(self, request):
print("Here: working")
#action(detail=True, methods=['post'])
def add(self, request, *arg, **kwargs):
print("HERE in custom action")
order = self.get_object()
print(order)
my app's urls.py is:
from rest_framework import routers
from .views import OrderAPIViewSet
router = routers.DefaultRouter()
router.register(r'orders', OrderAPIViewSet, basename='order')
urlpatterns = router.urls
So in my test when I try to access orders/post it works, but when I try to access orders/{pk}/add it fails. I mean, the reverse itself is failing:
ORDERS_LIST_URL = reverse('order-list')
ORDERS_ADD_URL = reverse('order-add')
class PublicOrderApiTests(TestCase):
def test_sample_test(self):
data = {}
res = self.client.post(ORDERS_ADD_URL, data, format='json')
as I said before, I've got a separate test where I use ORDERS_LIST_URL like this:
res = self.client.post(ORDERS_LIST_URL, data, format='json')
but when running the test I'm getting the following error:
ImportError: Failed to import test module: orders.tests Traceback
(most recent call last): File
"/usr/local/lib/python3.7/unittest/loader.py", line 436, in
_find_test_path
module = self._get_module_from_name(name) File "/usr/local/lib/python3.7/unittest/loader.py", line 377, in
_get_module_from_name
import(name) File "/app/orders/tests.py", line 22, in
ORDERS_ADD_URL = reverse('order-add') File "/usr/local/lib/python3.7/site-packages/django/urls/base.py", line 87,
in reverse
return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)) File "/usr/local/lib/python3.7/site-packages/django/urls/resolvers.py",
line 685, in _reverse_with_prefix
raise NoReverseMatch(msg) django.urls.exceptions.NoReverseMatch: Reverse for 'order-add' with no arguments not found. 2 pattern(s)
tried: ['orders/(?P[^/.]+)/add\.(?P[a-z0-9]+)/?$',
'orders/(?P[^/.]+)/add/$']
---------------------------------------------------------------------- Ran 1 test in 0.000s
FAILED (errors=1)
according to the documentation I shouldn't need to register this endpoint, the router is supposed to do it by itself. What am I missing?
The first thing that you've missed is pk in your reverse. Since the add API needs a pk of your Order object, you need to pass it to reverse function. For example:
order_add_url = reverse('order-add', kwargs={'pk': 1})
print(order_add_url) # which will print '/orders/1/add/'
So I think you should move this part to the body of PublicOrderApiTests's methods since you need a dynamic url per test object.
Another problem is that the ViewSet class does not support self.get_object() and if you want to use this method you should either have your own method or use rest framework GenericViewSet (i.e. from rest_framework.viewsets import GenericViewSet and inherit from this class instead of ViewSet) then you can access the get_object() method. You can also read more about generic views in rest framework docs.
I am trying to run pytest and am getting this error:
RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.
I have the following test in test_models.py that checks if the uuid has been added to the User (it is on autoadd):
import pytest
from backendapps.core.models import User
pytestmark = pytest.mark.django_db
class TestUserModel():
user = User.objects.create()
assert user.uuid is not None
I also have a fixtures file called conftest.py at the root level.
import pytest
from backendapps.core.models import Org, User
#pytest.fixture(autouse=True)
def enable_db_access(db):
pass
I then also have this pytest.ini also at the root level:
[pytest]
testpaths = backendapps
addopts = --ds=config.settings.local --reuse-db --create-db
python_files = tests.py test_*.py *_tests.py```
My test database is set to:
'TEST': {
'NAME': 'project_test_db',
},
Going through other posts, here are the debug steps I have taken:
add the pytestmark = pytest.mark.django_db line - I have this
Check the db permissions - my user has superuser permissions
Check for migration errors on your db - I migrated all my migrations to zero and reran them to check if I had any manual settings when I migrated and it was all fine.
Any thoughts on what to try or how to get a clearer error?
Full error:
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ERROR collecting backendapps/core/tests/test_models.py ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
backendapps/core/tests/test_models.py:18: in <module>
class TestUserModel():
backendapps/core/tests/test_models.py:27: in TestUserModel
user = User.objects.create()
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/models/manager.py:85: in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/models/query.py:447: in create
obj.save(force_insert=True, using=self.db)
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/models/base.py:753: in save
self.save_base(using=using, force_insert=force_insert,
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/models/base.py:790: in save_base
updated = self._save_table(
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/models/base.py:895: in _save_table
results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/models/base.py:933: in _do_insert
return manager._insert(
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/models/manager.py:85: in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/models/query.py:1249: in _insert
return query.get_compiler(using=using).execute_sql(returning_fields)
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/models/sql/compiler.py:1395: in execute_sql
with self.connection.cursor() as cursor:
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/utils/asyncio.py:26: in inner
return func(*args, **kwargs)
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/backends/base/base.py:259: in cursor
return self._cursor()
../../.local/share/virtualenvs/projectAPI-NVQT3lgx/lib/python3.8/site-packages/django/db/backends/base/base.py:235: in _cursor
self.ensure_connection()
E RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.
================================================================================= short test summary info ==================================================================================
FAILED backendapps/core/tests/test_models.py - RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Results (0.49s):
I guess this error is due to the fact that you're trying to create the User on the test object directly. Therefore, the code will be executed before the Database is setup and hence the error.
You might try to create the User in a test method:
class TestUserModel:
def test_user_uuid_is_not_none(self):
user = User.objects.create()
assert user.uuid is not None
Or you could simply just run a test function
def test_user_uuid_is_not_none(self):
user = User.objects.create()
assert user.uuid is not None
If you need access to the User several times in your Test, create a fixture and use it in the test:
[conftest.py]
#pytest.fixture
def user() -> settings.AUTH_USER_MODEL:
# return the UserFactory (factoryboy)
return UserFactory()
[test_models.py]
import pytest
from django.contrib.auth import get_user_model
pytestmark = pytest.mark.django_db
User = get_user_model()
class TestUserModel:
def test_user_uuid_is_not_none(self, user: User):
assert user.uuid is not None
From pytest docs:
By default your tests will fail if they try to access the database. Only if you explicitly request database access will this be allowed. This encourages you to keep database-needing tests to a minimum which is a best practice since next-to-no business logic should be requiring the database. Moreover it makes it very clear what code uses the database and catches any mistakes.
The easiest solution would be to add fixture as decorator:
#pytest.mark.django_db(True)
class TestUserModel():
I want to use #oidc.require_login to redirect login request to okta. I get AttributeError: '_AppCtxGlobals' object has no attribute 'oidc_id_token' error which I am unable to resolve
1) I built an app based on Flaskr tutorial that demonstrates use of flask factory method create_app.
2) I created a okta.py class as follows:
from oauth2client.client import OAuth2Credentials
from flask_oidc import OpenIDConnect
from okta import UsersClient
import click
from flask import current_app, g, session
from flask.cli import with_appcontext
oidc = OpenIDConnect()
def get_oidc():
"""
Connect to okta
"""
if 'oidc' not in g:
print('okta: get_oidc call')
g.oidc = OpenIDConnect(current_app)
g.okta_client = UsersClient("<okta-server-url>", "<secret>")
# fixing global oidc problem for decorator in rooms
oidc = g.oidc
return g.oidc
def close_oidc(app):
""" Release okta connection
"""
oidc = g.pop('oidc',None)
if oidc is not None:
oidc.logout()
# with app.app_context():
# session.clear()
def init_okta():
"""Connect to existing table"""
oidc = get_oidc()
""" Can do additional initialization if required """
#click.command('init-okta')
#with_appcontext
def init_okta_command():
"""Connect to existing oidc"""
init_okta()
click.echo (get_oidc())
click.echo('Initialized Okta.')
print('Initialized Okta.')
def init_app(app):
"""Register okta functions with the Flask app. This is called by
the application factory.
"""
app.teardown_appcontext(close_oidc)
app.cli.add_command(init_okta_command)
3) My goal is to use okta login to browse room listings
from flask import (
Blueprint, flash, g, redirect, render_template,
request, url_for, current_app, session, jsonify
)
from werkzeug.exceptions import abort
...
from hotel.okta import oidc, get_oidc, init_app
bp = Blueprint('rooms', __name__)
...
#bp.route('/login', methods=['GET', 'POST'])
#oidc.require_login
def login():
"""
Force the user to login, then redirect them to the get_books.
Currently this code DOES NOT work
Problem:
* oidc global object is not available to pass request to okta
Resolution:
* redirecting to rooms.calendar
"""
# info = oidc.user_getinfo(['preferred_username', 'email', 'sub'])
# id_token = OAuth2Credentials.from_json(oidc.credentials_store[info.get('sub')]).token_response['id_token']
return redirect(url_for("rooms.calendar"))
4) My __init__.py looks like this
import os
from flask import Flask
from flask_oidc import OpenIDConnect
from okta import UsersClient
# This is a factory method for productive deployment
# Use app specific configuration
# For any app local files, use /instnce Folder
def create_app(test_config=None):
"""Create and configure an instance of the Flask application."""
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
# a default secret that should be overridden by instance config
SECRET_KEY='dev',
# store the database in the instance folder
DATABASE=os.path.join(app.instance_path, 'hotel.sqlite'),
OIDC_CLIENT_SECRETS=os.path.join(app.instance_path, 'client_secrets.json'),
OIDC_COOKIE_SECURE=False,
OIDC_CALLBACK_ROUTE= '/oidc/callback',
OIDC_SCOPES=["openid", "email", "profile"],
OIDC_ID_TOKEN_COOKIE_NAME = 'oidc_token',
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.update(test_config)
# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
# # register the database commands
from hotel import db
db.init_app(app)
# apply the blueprints to the app
from hotel import rooms
app.register_blueprint(rooms.bp)
# for Okta
# Ref: https://www.fullstackpython.com/blog/add-user-authentication-flask-apps-okta.html
from hotel import okta
with app.app_context():
okta.init_app(app)
#app.route('/hello') # For testing factory method
def hello():
return 'Hello, World!'
# make url_for('index') == url_for('blog.index')
# in another app, you might define a separate main index here with
# app.route, while giving the blog blueprint a url_prefix, but for
# the tutorial the blog will be the main index
app.add_url_rule('/', endpoint='index')
return app
5) Here is the code snippet of rooms.before_request
#bp.before_request
def before_request():
print ('rooms.before_request call reached')
with current_app.app_context():
print ('rooms.before_request in app_context',g)
oidc = g.pop('oidc',None)
okta_client = g.pop('okta_client',None)
if oidc is not None and okta_client is not None:
print ('rooms.before_request g.oidc and g.okta_client available')
if oidc.user_loggedin:
# OpenID Token as
g.user = okta_client.get_user(oidc.user_getfield("sub"))
g.oidc_id_token = OAuth2Credentials.from_json(g.oidc.credentials_store[info.get('sub')]).token_response['id_token']
else:
g.user = None
else:
print('rooms.beforerequest No user logged in')
g.user = None
My Analysis:
okta.init_app(app) is expected to use client_secrets.json in /instance folder to connect to okta.
In order to make #oidc.require_login to work, I exposed oidc in okta.get_oidc and import this into my rooms.py code
However, this does not work :(! Stack trace is:
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 2328, in __call__
return self.wsgi_app(environ, start_response)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 2314, in wsgi_app
response = self.handle_exception(e)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1760, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/_compat.py", line 36, in reraise
raise value
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 2311, in wsgi_app
response = self.full_dispatch_request()
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1834, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1737, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/_compat.py", line 36, in reraise
raise value
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1832, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask/app.py", line 1818, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/flask_oidc/__init__.py", line 485, in decorated
if g.oidc_id_token is None:
File "/Users/athur/Code/cmpe272WarriorsHotel/Hotel/venv/lib/python3.7/site-packages/werkzeug/local.py", line 348, in __getattr__
return getattr(self._get_current_object(), name)
AttributeError: '_AppCtxGlobals' object has no attribute 'oidc_id_token'
My questions:
How can I call #oidc.require_login in flask application factory
method?
Is the way I initialize okta connection correct?
Is there anyway to manually call external authorization server without decorator approach? How?
Is setting flask stack variables a better way to go forward? If so, how?
Trying to set g in before_request does not seem to work. What are the other options to work in Flask's App Factory approach?
Any insights will be much appreciated!
Thanks!
Yuva
I'm back with more Django Class Based View questions. I have an "extended" user model in the form of a "profile" model. The profile model is using CBVs to implement CRUD functionality but the DeleteView always generates FOREIGN KEY constraint failed IntegrityError exception. I know what that should mean but I am not sure why I am getting that exception.
I have the built-in Django user model plus an "account adaptor" and my custom Profile model. The account adaptor just sets the signup email address as the username:
class AccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
Log.add("")
data = form.cleaned_data
user.username = data['email'] # username not in use
user.email = data['email']
if 'password1' in data:
user.set_password(data['password1'])
else:
user.set_unusable_password()
self.populate_username(request, user)
if commit:
user.save()
return user
The Profile model has a OneToOneField to attach a profile instance to a user.
class Profile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
blank=False,
null=False,
)
The DeleteView CBV is:
#method_decorator(verified_email_required, name='dispatch')
class Delete(DeleteView):
pk_url_kwarg = "account_number"
model = Profile
form_class = ProfileForm
success_url = "/accounts/logout.html"
def get(self, request, *args, **kwargs):
try:
profile = self.model.objects.get(account_number=self.kwargs[self.pk_url_kwarg])
user = User.objects.get(pk=profile.user.pk)
user.delete()
messages.success(request, "The user is deleted")
my_render = render(request, self.success_url)
except User.DoesNotExist:
messages.error(request, "User does not exist")
my_render = render(request, self.success_url)
except IntegrityError:
messages.error(request, "DB IntegrityError")
my_render = render(request, self.success_url)
return my_render
In the Delete.get method I can put a breakpoint on the user.delete() line and I can see that the profile and user objects are what I think they should be. I try to delete the user object and I get the IntegrityError exception listed above.
The stack-trace looks like:
Environment:
Request Method: GET
Request URL: http://127.0.0.1:8000/Members/Profile/d83622e4-4816-42a4-8419-2fd389c7e3fd/delete?csrfmiddlewaretoken=y3W0ze1SfN50Mx3eymEZQQPd21u5wjf0tHvRZM0PggLX12mdAgdEGUkw3lw2KnKn
Django Version: 2.0.3
Python Version: 3.6.3
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_extensions',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'auditlog',
'widget_tweaks',
'Members.apps.MembersConfig']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'auditlog.middleware.AuditlogMiddleware']
Traceback:
File "C:\Program Files\Python36\lib\site-packages\django\db\backends\base\base.py" in _commit
239. return self.connection.commit()
The above exception (FOREIGN KEY constraint failed) was the direct cause of the following exception:
File "C:\Program Files\Python36\lib\site-packages\django\core\handlers\exception.py" in inner
35. response = get_response(request)
File "C:\Program Files\Python36\lib\site-packages\django\core\handlers\base.py" in _get_response
128. response = self.process_exception_by_middleware(e, request)
File "C:\Program Files\Python36\lib\site-packages\django\core\handlers\base.py" in _get_response
126. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Program Files\Python36\lib\site-packages\django\views\generic\base.py" in view
69. return self.dispatch(request, *args, **kwargs)
File "C:\Program Files\Python36\lib\site-packages\django\utils\decorators.py" in _wrapper
62. return bound_func(*args, **kwargs)
File "C:\Program Files\Python36\lib\site-packages\django\contrib\auth\decorators.py" in _wrapped_view
21. return view_func(request, *args, **kwargs)
File "C:\Program Files\Python36\lib\site-packages\allauth\account\decorators.py" in _wrapped_view
32. return view_func(request, *args, **kwargs)
File "C:\Program Files\Python36\lib\site-packages\django\utils\decorators.py" in bound_func
58. return func.__get__(self, type(self))(*args2, **kwargs2)
File "C:\Program Files\Python36\lib\site-packages\django\views\generic\base.py" in dispatch
89. return handler(request, *args, **kwargs)
File "C:/Users/Me/PycharmProjects/MyProject/MyApp\Members\views\profile.py" in get
103. user.delete()
File "C:\Program Files\Python36\lib\site-packages\django\db\models\base.py" in delete
891. return collector.delete()
File "C:\Program Files\Python36\lib\site-packages\django\db\models\deletion.py" in delete
307. sender=model, instance=obj, using=self.using
File "C:\Program Files\Python36\lib\site-packages\django\db\transaction.py" in __exit__
212. connection.commit()
File "C:\Program Files\Python36\lib\site-packages\django\db\backends\base\base.py" in commit
261. self._commit()
File "C:\Program Files\Python36\lib\site-packages\django\db\backends\base\base.py" in _commit
239. return self.connection.commit()
File "C:\Program Files\Python36\lib\site-packages\django\db\utils.py" in __exit__
89. raise dj_exc_value.with_traceback(traceback) from exc_value
File "C:\Program Files\Python36\lib\site-packages\django\db\backends\base\base.py" in _commit
239. return self.connection.commit()
Exception Type: IntegrityError at /Members/Profile/d83622e4-4819-42d4-8419-2fd389c7e3fd/delete
Exception Value: FOREIGN KEY constraint failed
What am I doing wrong?
EDIT
I think my problem is with sqlite3. My DB backend is sqlite3. I have just discovered that the Django migrations that create the Profile model table DO create a foreign key reference from the profile to the User model but an on delete cascade clause is NOT created. The constraint Django creates looks like:
FOREIGN KEY(`user_id`) REFERENCES `auth_user`(`id`) DEFERRABLE INITIALLY DEFERRED,
I added the on delete cascade option by hand:
FOREIGN KEY(`user_id`) REFERENCES `auth_user`(`id`) on delete cascade DEFERRABLE INITIALLY DEFERRED,
but the delete operation failed as above. I dropped the DEFERRABLE INITIALLY DEFERRED clause and still get the violation.
I have a sqlite gui "management" tool and just tried to delete a user record using that management tool and get a foreign key violation as well so this must be on the sqlite side of things.
Edit 2
After more investigation I see the following: I am new to python and Django. My Django test app is really very small and I am not doing any/many custom actions. Using sqlite as the DB backend and doing the initial project makemigrations and migrate created the standard Django and django-allauth tables. There are a few of these base tables that have FOREIGN KEY relationships with the user table. My last survey of the DB was not extremely rigorous as it was really late last night ... but those that do reference the user table do not have on delete cascade clauses. So, even if I fix "my" table(s) the base Django tables that reference user seem to be "broken" by not having the cascade clause.
I will file a bug report if I can figure out where to send it.
Well, I have learned something. Django does not currently support the on delete cascade DB clause. It looks like you have to implement the behaviour yourself. I have seen posts in the email "Django Users" group about using signals for this.
I decided to try it in the DeleteView.get() method.
class Delete(DeleteView):
pk_url_kwarg = "account_number"
model = Profile
# form_class = ProfileForm
success_url = "account/login.html"
def get(self, request, *args, **kwargs):
try:
profile = get_object_or_404(Profile, account_number=self.kwargs[self.pk_url_kwarg])
user_pk = profile.user.pk
profile.delete()
get_object_or_404(User, pk=user_pk).delete()
messages.success(request, "The user is deleted")
except User.DoesNotExist:
messages.error(request, "User does not exist")
# except IntegrityError:
# messages.error(request, "DB IntegrityError")
return render(request, self.success_url)
It is possible that I don't have to actually delete the profile instance but I am still testing.
I seem to have the same problem as in this question: Django logout problem
Mine is a bit weirder, it works in google chrome.... but not in firefox...
this is my logout function: (in views.py)
def my_logout(request):
logger.debug("Logout called by user")
try:
# Here I do some custom stuff, like logging this action in a database and so on
# For this question it shouldn't matter... because in a try catch
# so whatever goes wrong here, logging out should work anyway
except Exception, e:
logger.info("Logging logout action error: %s" % e)
logout(request)
return HttpResponseRedirect("/")
in settings.py I have:
LOGIN_URL = '/desktop/login/'
LOGOUT_URL = '/desktop/logout/'
LOGIN_REDIRECT_URL = '/'
And in the urls.py of the app iamapps (include in the project urls as /desktop/):
url(r'^login/$', 'iamapps.views.my_login', name='iamapps.login'),
url(r'^logout/$', 'iamapps.views.my_logout', name='iamapps.logout'),
further info:
django 1.4.3 (just updated from 1.3 to 1.4 ....)
python 2.7
works in Chrome but not in Firefox 17.0.1, Linux
The fact that it does work in google chrome but does not work in firefox puzzles me the most. Seems it has something to do with firefox keeps remembering the user as being logged on...
EDIT:
I get a broken pipe.... but I seem to get it not on logging out... but going to the home view after logout....
Traceback (most recent call last):
File "/usr/lib/python2.7/wsgiref/handlers.py", line 86, in run
self.finish_response()
File "/usr/lib/python2.7/wsgiref/handlers.py", line 127, in finish_response
self.write(data)
File "/usr/lib/python2.7/wsgiref/handlers.py", line 210, in write
self.send_headers()
File "/usr/lib/python2.7/wsgiref/handlers.py", line 268, in send_headers
self.send_preamble()
File "/usr/lib/python2.7/wsgiref/handlers.py", line 192, in send_preamble
'Date: %s\r\n' % format_date_time(time.time())
File "/usr/lib/python2.7/socket.py", line 324, in write
self.flush()
File "/usr/lib/python2.7/socket.py", line 303, in flush
self._sock.sendall(view[write_offset:write_offset+buffer_size])
error: [Errno 32] Broken pipe
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 58684)
Traceback (most recent call last):
File "/usr/lib/python2.7/SocketServer.py", line 582, in process_request_thread
self.finish_request(request, client_address)
File "/usr/lib/python2.7/SocketServer.py", line 323, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/media/storage/django/sites/iamfloraservice/parts/django/django/core/servers/basehttp.py", line 139, in __init__
super(WSGIRequestHandler, self).__init__(*args, **kwargs)
File "/usr/lib/python2.7/SocketServer.py", line 641, in __init__
self.finish()
File "/usr/lib/python2.7/SocketServer.py", line 694, in finish
self.wfile.flush()
File "/usr/lib/python2.7/socket.py", line 303, in flush
self._sock.sendall(view[write_offset:write_offset+buffer_size])
error: [Errno 32] Broken pipe
----------------------------------------
[24/Dec/2012 14:33:25] "GET / HTTP/1.1" 200 48247
Edit 2
it goes to this view after loging out and being redirected:
def home(request, template='iamfloraservice/home.html'):
logger.debug("Home view called by user %s" % request.user)
return render_to_response(template,{},context_instance=RequestContext(request))
I think the redirect with the request to this view causes the problem....
In the log sais it's still user 'michel' (beacuse the view uses the request from the redirect, and that had user michel)... however... user michel is being logged out in the mean time....
EDIT 3
because of the suggestion it's due to the logger.
unremarking the logging does not help
And it's the default logger:
import logging
logger = logging.getLogger(__name__)
EDIT 4 (30-12-2012)
My logout is from a main window where I show a logout link when a user is logged on and a login link if a user is logged out. Also it contains a toolbar, which tools are filled depending on the the user and it's membership of groups.
I think the problem is, it's reloading this main window while the cache and the user in it's request isn't cleared yet. Somehow Chrome knows how to handle this, and firefox results in a broken pipe error. Clearing the cache manually in the browser results in the correct view after reload....
a solution might be to redirect to a page without anything in it that contains users...
or find out to clear the cache on the right moment myselve....
this problem describes maybe the same... but I cannot expect users to do anything in the browser just to logout? see django 1.4 caching GET to /login/
Edit 5 (31-12-2012)
It seems it's a caching problem.... but not sure how to fix this yet.
these are my caching settings:
if not DEBUG:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
else:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
But I tried without the dummycache as well
EDIT 6 (4-jan-2013)
Still no solution.... I changed the way I log out to the django way, and I Am using the signals now... see my own answer below.
but still it gives the brokenpipe error which causes firefox stopping logging out. It's not a caching problem. If I go to another page, or even worse... the admin pages. I Am still logged on. The only way to logout is through the logout on the admin page.... If it's not an admin user... there is no way to get me logged out on the firefox browser.
When logging off using the admin interface, so the signal works fine...
I checked by turning off the signal.... and still the logging out in firefox does not work.
Conclusion: going back to the main page () is causing the problem.
EDIT 7 (4 jan 2013)
I made a simple loggedout view for testing,
this template:
<html>
<head>
<title>
Logged out
</title>
</head>
<body>
You are succesfully logged out.<br>
<br>
Go back to the main page
or<br>
log in again
</body>
</html>
and the logged out view:
class LoggedOutView(TemplateView):
template_name = "iamapps/logged_out.html"
and changed the urls in to:
url(r'^logout/$', 'django.contrib.auth.views.logout', {'next_page': '/desktop/loggedout/'}, name='iamapps.logout'),
#url(r'^logout/$', 'django.contrib.auth.views.logout_then_login', name='iamapps.logout'),
url(r'^loggedout/$', LoggedOutView.as_view(),name='iamapps.loggedout'),
and still, to simplyfy things... I have the signals turned off.
and it's still not working in firefox.... but it works in chrome
In firefox it does not go to the logged out page
I generally just use the contributed view to logout users. Simply add this to your root urls.py file:
# Would be nice to use settings.LOGIN_URL for `next_page` here, too
url(r'^logout/$', 'django.contrib.auth.views.logout', {'next_page': '/login'}),
and you'll be good to go.
Happy Djangoing.
Finally I totally switched to almost back to django defaults...
Using:
in views.py:
from django.contrib.auth.forms import AuthenticationForm
from django.views.generic.edit import FormView
class LoginView(FormView):
"""
This is a class based version of django.contrib.auth.views.login.
"""
form_class = AuthenticationForm
redirect_field_name = REDIRECT_FIELD_NAME
template_name = 'iamapps/login.html'
#method_decorator(csrf_protect)
#method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
return super(LoginView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
"""
The user has provided valid credentials (this was checked in AuthenticationForm.is_valid()). So now we
can check the test cookie stuff and log him in.
"""
self.check_and_delete_test_cookie()
login(self.request, form.get_user())
return super(LoginView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(LoginView, self).get_context_data(**kwargs)
apps_settings=iamapps_settings()
if apps_settings[LOGON_BASE_APP_NAME]:
self.extend_template="%s/base.html" % apps_settings[LOGON_BASE_APP_NAME]
else:
self.extend_template="iamapps/base.html"
context['extend_template']=self.extend_template
return context
def form_invalid(self, form):
"""
The user has provided invalid credentials (this was checked in AuthenticationForm.is_valid()). So now we
set the test cookie again and re-render the form with errors.
"""
self.set_test_cookie()
return super(LoginView, self).form_invalid(form)
def get_success_url(self):
if self.success_url:
redirect_to = self.success_url
else:
redirect_to = self.request.REQUEST.get(self.redirect_field_name, '')
netloc = urlparse.urlparse(redirect_to)[1]
if not redirect_to:
redirect_to = settings.LOGIN_REDIRECT_URL
# Security check -- don't allow redirection to a different host.
elif netloc and netloc != self.request.get_host():
redirect_to = settings.LOGIN_REDIRECT_URL
return redirect_to
def set_test_cookie(self):
self.request.session.set_test_cookie()
def check_and_delete_test_cookie(self):
if self.request.session.test_cookie_worked():
self.request.session.delete_test_cookie()
return True
return False
def get(self, request, *args, **kwargs):
"""
Same as django.views.generic.edit.ProcessFormView.get(), but adds test cookie stuff
"""
self.set_test_cookie()
return super(LoginView, self).get(request, *args, **kwargs)
and urls:
url(r'^login/$', LoginView.as_view(), name='login'),
This solved all my troubles... about loggin and logiing off on...
the login and logout signals just working fine:
from django.contrib.auth.signals import user_logged_out, user_logged_in
# Note, these login and logout signals are registered in imamstats views
def iam_logged_out_actions(sender, user, request, **kwargs):
try:
# ... do my logging out actiosn (stats etc.)
except Exception, e:
logger.error("Logging logout action error: %s" % e)
# Note, these login and logout signals are registered in imamstats views
def iam_logged_in_actions(sender, user, request, **kwargs):
try:
# ... do my log in stats etc. things
except Exception, e:
logger.error("Logging login action error: %s" % e)
browsing to stackoverflow... (did another search)
I found this one: .... see django.contrib.auth.logout in Django ..
But is is even worse.... I found this... astonished... but explains it all:
Django, Logout_URL doesn't redirect well
The I found out it's a won't fix bug (###$%%) not allowed to curse on christmas eve....
So the solution to do my custom stuff is in the signals in stead of using my own view.
Do the default view and redirect...
and use this documentation to create a signal.. https://docs.djangoproject.com/en/dev/topics/auth/#login-and-logout-signals
Adding the signals is quite easy, i put it in models.py off my main app (iamapps):
import logging
from django.contrib.auth.signals import user_logged_out
from django.contrib.auth.signals import user_logged_in
logger = logging.getLogger(__name__)
def iam_logged_out_actions(sender, user, request, **kwargs):
#whatever...
logger.debug("Logging out: user = %s" % user)
user_logged_out.connect(iam_logged_out_actions)
def iam_logged_in_actions(sender, user, request, **kwargs):
#whatever...
logger.debug("Logging in: user = %s" % user)
user_logged_in.connect(iam_logged_in_actions)
This works....however it does not solve the broken pipe which I think might cause the failure on the logging out... so logging out in firefox still fails... and in chrome it works...
Logging out from django admin page works in firefox.. and the signal has another pro: also from logging out in the admin interface, the signal is being called...
for logging out i use now this urls.py:
url(r'^logout/$', 'django.contrib.auth.views.logout', {'next_page': '/'}, name='iamapps.logout'),
My suspicion is that the original logout view returns a response that clears cookie or something like that, and you're throwing that response out. Could you try simply returning its response to the user, like this?
def my_logout(request):
# something...
return logout(request)
The verified answer work well for the mentioned version but it is not working in django 2.2 version any more. so in order to do successful redirect after logout you have to define the attributes.
from django.contrib import admin
from django.urls import path, include
from dark_bot import views
from django.contrib.auth import views as v
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.index, name="index"),
path('contact/', views.contact, name="contact"),
path('search/', include('search.urls')),
path('user/', include('accounts.urls')),
path('dashboard/', include('dashboard.urls')),
path('accounts/login/', v.LoginView.as_view(), name="login"),
path('accounts/logout/',v.LogoutView.as_view(next_page='/'),name="logout")
# path('dashboard/', include('dashboard.urls')),
]
This is how you can see I passed the next_page attribute to the class LogoutView which will tell where to redirect or go after successfull logout.
Hope this may help someone.
Happy Coding!