Good day everyone.
I would like to ask you if this incorrect behaviour is something known.
First, I am using Django 1.8.2, Python 3.4.3, Python Tools for Visual Studio 2.2RC, VS2013, Windows 8.1, msysgit 1.9.5.
A made a unit test which was supposed to fail, and it did fail from the visual studio test runner, but it was successfully passed when I ran it from the command line using manage.py test command.
I run tests in command as follows: manage.py test --settings=monodemo.settings.dev_russ
This assert does not fail in Django test runner when my Form View (CBV) has a success_url=’dashboard:dashboard’:
self.assertEqual(self.client.session[SESSION_KEY], str(user.pk))
But it does fail in VS and it should because the return status code was 400 in any case. In visual studio unit tests, it was raising KeyError:’_auth_user_id’.
Also, I realised that I forgot to add ‘dashboard’ project into installed_apps but it was not affecting the result of the test.
My dependencies:
Django==1.8.2
psycopg2==2.6 (taken from here http://www.stickpeople.com/projects/python/win-psycopg/ and then manually installed into the virtual environment; direct download link: http://www.stickpeople.com/projects/python/win-psycopg/2.6.0/psycopg2-2.6.0.win32-py3.4-pg9.4.1-release.exe )
Unipath==1.1
Please advise if this is something to worry or if I have improperly configured something.
Thank you all in advance!
Ruslan (Russ) Bazhenov
I have attached pretty much all the related code down there. (Tried to exclude what was not related, though)
#monodemo/registration/tests/base.py
import django
from django.test import TestCase
from django.test.utils import setup_test_environment
from django.contrib.auth.models import User
class AbstractTestCase(TestCase):
"""Utility base class for all tests for this app as it contains helper functions"""
@classmethod
def setUpClass(cls):
"""One time Django setup sequence.
It is needed for correct testing outside of django test runner
For example in the UnitTest test runner or the one used in VS"""
setup_test_environment()
django.setup()
super().setUpClass()
def prepare_user(self, username):
"""Creates, saves and returns test user"""
# An interesting observation
# By default a user iscreated ACTIVE
return User.objects.create_user(username=username,
email='te...@example.com',
password='glassfish100500'
)
#monodemo/registration/tests/test_views.py
from django.core.urlresolvers import reverse
from django.contrib.auth import SESSION_KEY
from registration.forms import UserLoginForm
from .base import AbstractTestCase
class LoginViewTests(AbstractTestCase):
"""Tests for the Registration app views."""
def test_can_login(self):
"""Verifies that a user can authenticate"""
user = super().prepare_user('Sucker')
email = 'te...@example.com'
password = 'glassfish100500'
response = self.client.post(reverse('home_page'), data={'email':email,
'password':password,
'remember_me':True
})
self.assertEqual(self.client.session[SESSION_KEY], str(user.pk))
#monodemo/registration/views.py
from django.views.generic import FormView
from django.contrib.auth import login
from registration.forms import UserLoginForm
class LoginView(FormView):
"""User authentication view"""
template_name = 'registration/registration_login.html'
form_class = UserLoginForm
success_url = 'dashboard:dashboard'
def form_valid(self, form):
"""Logs a verified user in after form is validated"""
user = form.get_user()
login(self.request, user)
return super().form_valid(form)
#monodemo/registration/forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
class UserLoginForm(forms.Form):
"""Verifies login details"""
email = forms.EmailField(widget=forms.TextInput(attrs={'class': 'text',
'placeholder': 'Email',
}))
password = forms.CharField(max_length=128,
widget=forms.PasswordInput(attrs={'class': 'text',
'placeholder': 'Password',
}))
remember_me = forms.BooleanField(required=False,
label='remember me',
widget=forms.CheckboxInput(attrs={'class': 'checkbox',}))
def clean(self):
"""User verification. Other data does not require validation
because it will be handled by authenticate()
"""
cleaned_data = super(UserLoginForm, self).clean()
email = cleaned_data.get("email")
password = cleaned_data.get("password")
try:
user = User.objects.get(email=email)
except (User.DoesNotExist, User.MultipleObjectsReturned):
raise forms.ValidationError("The email address or password were incorrect")
user = authenticate(username=user.username, password=password)
if user is None:
raise forms.ValidationError("The email address or password were incorrect")
if user.is_active != True:
raise forms.ValidationError("The email address or password were incorrect")
# Dirty hack or fancy Python?
def get_user():
return user
self.get_user = get_user
#monodemo/monodemo/urls.py
from django.conf.urls import include, url
from django.contrib import admin
from registration.views import LoginView
urlpatterns = [
url(r'^$', LoginView.as_view(), name='home_page'),
url(r'^auth/', include('registration.urls', namespace='registration')),
url(r'^dashboard/', include('dashboard.urls', namespace='dashboard')),
url(r'^admin/', include(admin.site.urls)),
]
#monodemo/registration/urls.py
from django.conf.urls import include, url
urlpatterns = [
]
#monodemo/dashboard/urls.py
from django.conf.urls import url
from .views import LoginView
urlpatterns = [
url(r'^$', LoginView.as_view(), name='dashboard'),
]
#monodemo/dashboard/views.py
from django.views.generic import TemplateView
class LoginView(TemplateView):
template_name = 'dashboard/base_dashboard.html'
#monodemo/monodemo/settings/base.py
import json
from django.core.exceptions import ImproperlyConfigured
from unipath import Path
BASE_DIR = Path(__file__).ancestor(3)
MEDIA_ROOT = BASE_DIR.child("media")
STATIC_ROOT = BASE_DIR.child("static")
STATIC_URL = "/static/"
STATICFILES_DIRS = (
BASE_DIR.child("assets"),
)
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# JSON-based secrets module
with open(BASE_DIR.child("monodemo", "settings", "secrets.json")) as f:
secrets = json.loads(f.read())
def get_secret(setting, secrets=secrets):
"""Get the secret variable or return explicit exception."""
try:
return secrets[setting]
except KeyError:
error_msg = "Set the {0} environment variable".format(setting)
raise ImproperlyConfigured(error_msg)
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = get_secret("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'registration'
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
)
ROOT_URLCONF = 'monodemo.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': (
BASE_DIR.child("templates"),
),
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.request',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'monodemo.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': get_secret("NAME"),
'USER': get_secret("USER"),
'PASSWORD': get_secret("PASSWORD"),
}
}
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-GB'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
#monodemo/monodemo/settings/local.py
from .base import *
DEBUG = True
#monodemo/monodemo/settings/dev_russ.py
from .local import *