commit
5f4aefa722
|
|
@ -43,5 +43,3 @@ server/media/
|
|||
|
||||
.coverage
|
||||
|
||||
# Cypress screenshots
|
||||
client/cypress/screenshots
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ aliases:
|
|||
caches:
|
||||
- pip
|
||||
- node
|
||||
artifacts:
|
||||
- client/cypress/**/*.png
|
||||
- client/cypress/**/*.mp4
|
||||
services:
|
||||
- postgres
|
||||
script:
|
||||
|
|
|
|||
|
|
@ -13,3 +13,7 @@ cypress/videos
|
|||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
|
||||
# Cypress
|
||||
cypress/screenshots
|
||||
cypress/videos
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"baseUrl": "http://localhost:8000",
|
||||
"video": false
|
||||
"videoUploadOnPasses": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
describe('The Login Page', () => {
|
||||
it('login and redirect to main page', () => {
|
||||
const username = 'test';
|
||||
const password = 'test';
|
||||
|
||||
cy.visit('/');
|
||||
cy.login(username, password, true);
|
||||
cy.get('body').contains('Neues Wissen erwerben');
|
||||
});
|
||||
|
||||
it('user sees error message if username is omitted', () => {
|
||||
const username = '';
|
||||
const password = 'test';
|
||||
|
||||
cy.visit('/');
|
||||
cy.login(username, password);
|
||||
cy.get('[data-cy=email-local-errors]').contains('ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('user sees error message if password is omitted', () => {
|
||||
const username = 'test';
|
||||
const password = '';
|
||||
|
||||
cy.visit('/');
|
||||
cy.login(username, password);
|
||||
cy.get('[data-cy=password-local-errors]').contains('ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('user sees error message if credentials are invalid', () => {
|
||||
const username = 'test';
|
||||
const password = '12345';
|
||||
|
||||
cy.visit('/');
|
||||
cy.login(username, password);
|
||||
cy.get('[data-cy=login-error]').contains('Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.');
|
||||
});
|
||||
|
||||
it('redirect after login', () => {
|
||||
const username = 'test';
|
||||
const password = 'test';
|
||||
|
||||
cy.visit('/book/topic/berufliche-grundbildung');
|
||||
cy.login(username, password);
|
||||
cy.get('body').contains('Berufliche Grundbildung');
|
||||
});
|
||||
// it('logs in programmatically without using the UI', () => {
|
||||
// cy.visit('/accounts/login/'); // have to get a csrf token by getting the base page first
|
||||
//
|
||||
// const username = 'test';
|
||||
// const password = 'test';
|
||||
//
|
||||
// cy.getCookie('csrftoken').then(token => {
|
||||
// const options = {
|
||||
// url: '/accounts/login/',
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'X-CSRFToken': token.value,
|
||||
// 'content-type': 'multipart/form-data'
|
||||
// },
|
||||
// from: true,
|
||||
// body: {
|
||||
// username: username,
|
||||
// password: password,
|
||||
// // csrfmiddlewaretoken: token.value
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// cy.request(options);
|
||||
// cy.getCookie('sessionid').should('exist');
|
||||
// cy.visit('/');
|
||||
//
|
||||
// cy.get('.start-page__title').should('contain', 'skillbox')
|
||||
//
|
||||
//
|
||||
// // cy.visit('/');
|
||||
// // cy.getCookie('csrftoken')
|
||||
// // .then((csrftoken) => {
|
||||
// // const response = cy.request({
|
||||
// // method: 'POST',
|
||||
// // url: '/login/',
|
||||
// // form: true,
|
||||
// // body: {
|
||||
// // identification: username,
|
||||
// // password: password,
|
||||
// // csrfmiddlewaretoken: csrftoken.value
|
||||
// // }
|
||||
// // });
|
||||
// // });
|
||||
//
|
||||
// });
|
||||
|
||||
|
||||
// })
|
||||
})
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
describe('The Login Page', () => {
|
||||
it('login and redirect to main page', () => {
|
||||
const username = 'test';
|
||||
const password = 'test';
|
||||
|
||||
cy.visit('/');
|
||||
cy.login(username, password, true);
|
||||
cy.get('body').contains('Neues Wissen erwerben');
|
||||
});
|
||||
|
||||
it('user sees error message if username is omitted', () => {
|
||||
const username = '';
|
||||
const password = 'test';
|
||||
|
||||
cy.visit('/');
|
||||
cy.login(username, password);
|
||||
cy.get('[data-cy=email-local-errors]').contains('E-Mail ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('user sees error message if password is omitted', () => {
|
||||
const username = 'test';
|
||||
const password = '';
|
||||
|
||||
cy.visit('/');
|
||||
cy.login(username, password);
|
||||
cy.get('[data-cy=password-local-errors]').contains('Passwort ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('user sees error message if credentials are invalid', () => {
|
||||
const username = 'test';
|
||||
const password = '12345';
|
||||
|
||||
cy.visit('/');
|
||||
cy.login(username, password);
|
||||
cy.get('[data-cy=login-error]').contains('Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.');
|
||||
});
|
||||
|
||||
it('redirect after login', () => {
|
||||
const username = 'test';
|
||||
const password = 'test';
|
||||
|
||||
cy.visit('/book/topic/berufliche-grundbildung');
|
||||
cy.login(username, password);
|
||||
cy.get('body').contains('Berufliche Grundbildung');
|
||||
});
|
||||
|
||||
})
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
describe('The Regstration Page', () => {
|
||||
// works locally, but not in pipelines.
|
||||
// it('register user', () => {
|
||||
|
||||
// let timestamp = Math.round((new Date()).getTime() / 1000);
|
||||
|
||||
// const firstname = 'pesche';
|
||||
// const lastname = 'peschemann';
|
||||
// const email = `skillboxtest${timestamp}@iterativ.ch`;
|
||||
// const licenseKey = 'c1fa2e2a-2e27-480d-8469-2e88414c4ad8';
|
||||
|
||||
// cy.visit('/register');
|
||||
// cy.register(firstname, lastname, email, licenseKey);
|
||||
// cy.get('.reset__heading').contains('Schauen Sie in Ihr Postfach');
|
||||
// });
|
||||
|
||||
it('user sees error message if firstname is omitted', () => {
|
||||
let timestamp = Math.round((new Date()).getTime() / 1000);
|
||||
const firstname = '';
|
||||
const lastname = 'peschemann';
|
||||
const email = `skillboxtest${timestamp}@iterativ.ch`;
|
||||
const licenseKey = 'c1fa2e2a-2e27-480d-8469-2e88414c4ad8';
|
||||
|
||||
cy.visit('/register');
|
||||
cy.register(firstname, lastname, email, licenseKey);
|
||||
cy.get('[data-cy="firstname-local-errors"]').contains('Vorname ist ein Pflichtfeld.');
|
||||
});
|
||||
|
||||
it('user sees error message if lastname is omitted', () => {
|
||||
let timestamp = Math.round((new Date()).getTime() / 1000);
|
||||
const firstname = 'pesche';
|
||||
const lastname = '';
|
||||
const email = `skillboxtest${timestamp}@iterativ.ch`;
|
||||
const licenseKey = 'c1fa2e2a-2e27-480d-8469-2e88414c4ad8';
|
||||
|
||||
cy.visit('/register');
|
||||
cy.register(firstname, lastname, email, licenseKey);
|
||||
cy.get('[data-cy="lastname-local-errors"]').contains('Nachname ist ein Pflichtfeld.');
|
||||
});
|
||||
|
||||
it('user sees error message if email is omitted', () => {
|
||||
let timestamp = Math.round((new Date()).getTime() / 1000);
|
||||
const firstname = 'pesche';
|
||||
const lastname = 'peschemann';
|
||||
const email = ``;
|
||||
const licenseKey = 'c1fa2e2a-2e27-480d-8469-2e88414c4ad8';
|
||||
|
||||
cy.visit('/register');
|
||||
cy.register(firstname, lastname, email, licenseKey);
|
||||
cy.get('[data-cy="email-local-errors"]').contains('E-Mail ist ein Pflichtfeld.');
|
||||
});
|
||||
|
||||
it('user sees error message if license is omitted', () => {
|
||||
let timestamp = Math.round((new Date()).getTime() / 1000);
|
||||
const firstname = 'pesche';
|
||||
const lastname = 'peschemann';
|
||||
const email = `skillboxtest${timestamp}@iterativ.ch`;
|
||||
const licenseKey = '';
|
||||
|
||||
cy.visit('/register');
|
||||
cy.register(firstname, lastname, email, licenseKey);
|
||||
cy.get('[data-cy="licenseKey-local-errors"]').contains('Lizenz ist ein Pflichtfeld.');
|
||||
});
|
||||
|
||||
it('user sees error message if license key is wrong', () => {
|
||||
let timestamp = Math.round((new Date()).getTime() / 1000);
|
||||
const firstname = 'pesche';
|
||||
const lastname = 'peschemann';
|
||||
const email = `skillboxtest${timestamp}@iterativ.ch`;
|
||||
const licenseKey = 'asdsafsadfsadfasdf';
|
||||
|
||||
cy.visit('/register');
|
||||
cy.register(firstname, lastname, email, licenseKey);
|
||||
cy.get('[data-cy="licenseKey-remote-errors"]').contains('Die angegebenen Lizenz ist unglültig');
|
||||
});
|
||||
|
||||
})
|
||||
|
|
@ -82,3 +82,23 @@ Cypress.Commands.add('changePassword', (oldPassword, newPassword) => {
|
|||
}
|
||||
cy.get('[data-cy=change-password-button]').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('register', (firstname, lastname, email, licenseKey) => {
|
||||
if (firstname != '') {
|
||||
cy.get('[data-cy=firstname-input]').type(firstname);
|
||||
}
|
||||
|
||||
if (lastname != '') {
|
||||
cy.get('[data-cy=lastname-input]').type(lastname);
|
||||
}
|
||||
|
||||
if (email != '') {
|
||||
cy.get('[data-cy=email-input]').type(email);
|
||||
}
|
||||
|
||||
if (licenseKey != '') {
|
||||
cy.get('[data-cy=licenseKey-input]').type(licenseKey);
|
||||
}
|
||||
|
||||
cy.get('[data-cy=register-button]').click();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8985,8 +8985,7 @@
|
|||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
|
|
@ -9004,13 +9003,11 @@
|
|||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
|
@ -9023,18 +9020,15 @@
|
|||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
|
|
@ -9137,8 +9131,7 @@
|
|||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
|
@ -9148,7 +9141,6 @@
|
|||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
|
|
@ -9161,20 +9153,17 @@
|
|||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
|
|
@ -9191,7 +9180,6 @@
|
|||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
|
|
@ -9264,8 +9252,7 @@
|
|||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
|
|
@ -9275,7 +9262,6 @@
|
|||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
|
@ -9351,8 +9337,7 @@
|
|||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
|
|
@ -9382,7 +9367,6 @@
|
|||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
|
|
@ -9400,7 +9384,6 @@
|
|||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
|
|
@ -9439,13 +9422,11 @@
|
|||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -11449,8 +11430,7 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
|
|
@ -11475,8 +11455,7 @@
|
|||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
:class="{ 'skillboxform-input__input--error': errors.has('oldPassword') }"
|
||||
class="change-form__old skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-vv-as="Altes Passwort"
|
||||
data-cy="old-password">
|
||||
<small v-if="errors.has('oldPassword') && submitted" class="skillboxform-input__error" data-cy="old-password-local-errors">{{ errors.first('oldPassword') }}</small>
|
||||
<small v-for="error in oldPasswordErrors" :key="error" class=" skillboxform-input__error" data-cy="old-password-remote-errors">{{ error }}</small>
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
name="newPassword"
|
||||
type="text"
|
||||
v-model="newPassword"
|
||||
data-vv-as="Neues Passwort"
|
||||
v-validate="'required|min:8|strongPassword'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('newPassword') }"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
mutation Registration($input: RegistrationInput!){
|
||||
registration(input: $input) {
|
||||
success
|
||||
errors {
|
||||
field
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import veeDe from 'vee-validate/dist/locale/de';
|
|||
import {dateFilter} from './filters/date-filter';
|
||||
import autoGrow from '@/directives/auto-grow'
|
||||
import clickOutside from '@/directives/click-outside'
|
||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
|
|
@ -98,13 +99,22 @@ Validator.extend('strongPassword', {
|
|||
return strongRegex.test(value);
|
||||
}
|
||||
});
|
||||
|
||||
Validator.extend('email', {
|
||||
getMessage: field => 'Bitte geben Sie eine gülitge E-Mail an',
|
||||
validate: value => {
|
||||
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
|
||||
return emailRegex.test(value);
|
||||
}
|
||||
});
|
||||
|
||||
Vue.use(VeeValidate, {
|
||||
locale: 'de'
|
||||
});
|
||||
|
||||
Vue.filter('date', dateFilter);
|
||||
|
||||
/* logged in guard */
|
||||
/* guards */
|
||||
|
||||
function getCookieValue(cookieName) {
|
||||
// https://stackoverflow.com/questions/5639346/what-is-the-shortest-function-for-reading-a-cookie-by-name-in-javascript
|
||||
|
|
@ -112,18 +122,32 @@ function getCookieValue(cookieName) {
|
|||
return cookieValue ? cookieValue.pop() : '';
|
||||
}
|
||||
|
||||
function redirectIfLoginRequird(to) {
|
||||
function loginRequired(to) {
|
||||
// public pages have the meta.public property set to true
|
||||
return (!to.hasOwnProperty('meta') || !to.meta.hasOwnProperty('public') || !to.meta.public) && getCookieValue('loginStatus') !== 'true';
|
||||
return !to.hasOwnProperty('meta') || !to.meta.hasOwnProperty('public') || !to.meta.public;
|
||||
}
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (redirectIfLoginRequird(to)) {
|
||||
function unauthorizedAccess(to) {
|
||||
return loginRequired(to) && getCookieValue('loginStatus') !== 'true';
|
||||
}
|
||||
|
||||
function redirectStudentsWithoutClass() {
|
||||
return privateApolloClient.query({
|
||||
query: ME_QUERY,
|
||||
}).then(({data}) => data.me.schoolClasses.edges.length === 0 && data.me.permissions.length === 0);
|
||||
}
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
if (unauthorizedAccess(to)) {
|
||||
const redirectUrl = `/login?redirect=${to.path}`;
|
||||
next(redirectUrl);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
|
||||
if (to.name !== 'noClass' && loginRequired(to) && await redirectStudentsWithoutClass()) {
|
||||
router.push({name: 'noClass'})
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
/* eslint-disable no-new */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="login">
|
||||
<h1 class="login__title">Melden Sie sich jetzt an</h1>
|
||||
<div class="login public-page">
|
||||
<h1 class="login__title public-page__title">Melden Sie sich jetzt an</h1>
|
||||
<form class="login__form login-form" novalidate @submit.prevent="validateBeforeSubmit">
|
||||
<div class="login-form__field skillboxform-input">
|
||||
<label for="email" class="skillboxform-input__label">E-Mail</label>
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
type="text"
|
||||
v-model="email"
|
||||
v-validate="'required'"
|
||||
data-vv-as="E-Mail"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
|
||||
class="change-form__email skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
id="pw"
|
||||
name="password"
|
||||
type="password"
|
||||
data-vv-as="Passwort"
|
||||
v-model="password"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('password') }"
|
||||
|
|
@ -59,10 +61,11 @@
|
|||
<button class="button button--primary button--big actions__submit" data-cy="login-button">Anmelden</button>
|
||||
<a class="actions__reset text-link" href="/accounts/password_reset/">Passwort vergessen?</a>
|
||||
</div>
|
||||
<!--div class="registration">
|
||||
<div class="registration">
|
||||
<p class="registration__text">Haben Sie noch kein Konto?</p>
|
||||
<a class="registration__link text-link" href="/accounts/password_reset/">Jetzt registrieren</a>
|
||||
</div-->
|
||||
<router-link class="registration__link text-link" :to="{name: 'registration'}">Jetzt registrieren
|
||||
</router-link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -92,16 +95,24 @@ export default {
|
|||
store,
|
||||
{
|
||||
data: {
|
||||
login: { success }
|
||||
login
|
||||
}
|
||||
}
|
||||
) {
|
||||
try {
|
||||
if (success) {
|
||||
if (login.success) {
|
||||
const redirectUrl = that.$route.query.redirect ? that.$route.query.redirect : '/'
|
||||
that.$router.push(redirectUrl);
|
||||
} else {
|
||||
that.loginError = 'Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.';
|
||||
const firstError = login.errors[0];
|
||||
switch (firstError.field) {
|
||||
case 'invalid_credentials':
|
||||
that.loginError = 'Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.';
|
||||
break;
|
||||
case 'license_inactive':
|
||||
that.loginError = 'Ihre Lizenz ist nicht mehr aktiv.';
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
|
|
@ -137,15 +148,6 @@ export default {
|
|||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.login {
|
||||
&__title {
|
||||
margin-top: 48px;
|
||||
font-size: 2.75rem; // 44px
|
||||
margin-bottom: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.text-link {
|
||||
font-family: $sans-serif-font-family;
|
||||
color: $color-brand;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
<template>
|
||||
<div class="registration public-page">
|
||||
<h1 class="registration__title public-page__title">Registrieren Sie ihr persönliches Konto.</h1>
|
||||
<form class="registration__form registration-form" novalidate @submit.prevent="validateBeforeSubmit">
|
||||
<div class="registration-form__field skillboxform-input">
|
||||
<label for="firstname" class="skillboxform-input__label">Vorname</label>
|
||||
<input
|
||||
id="firstname"
|
||||
name="firstname"
|
||||
type="text"
|
||||
v-model="firstname"
|
||||
data-vv-as="Vorname"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('firstname') }"
|
||||
class="change-form__firstname skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="firstname-input"
|
||||
/>
|
||||
<small
|
||||
v-if="errors.has('firstname') && submitted"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="firstname-local-errors"
|
||||
>{{ errors.first('firstname') }}</small>
|
||||
<small
|
||||
v-for="error in firstnameErrors"
|
||||
:key="error"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="firstname-remote-errors"
|
||||
>{{ error }}</small>
|
||||
</div>
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<label for="lastname" class="skillboxform-input__label">Nachname</label>
|
||||
<input
|
||||
id="lastname"
|
||||
name="lastname"
|
||||
type="text"
|
||||
v-model="lastname"
|
||||
data-vv-as="Nachname"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('lastname') }"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="lastname-input"
|
||||
/>
|
||||
<small
|
||||
v-if="errors.has('lastname') && submitted"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="lastname-local-errors"
|
||||
>{{ errors.first('lastname') }}</small>
|
||||
<small
|
||||
v-for="error in lastnameErrors"
|
||||
:key="error"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="lastname-remote-errors"
|
||||
>{{ error }}</small>
|
||||
</div>
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<label for="email" class="skillboxform-input__label">E-Mail</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="text"
|
||||
v-model="email"
|
||||
data-vv-as="E-Mail"
|
||||
v-validate="'required|email'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="email-input"
|
||||
/>
|
||||
<small
|
||||
v-if="errors.has('email') && submitted"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="email-local-errors"
|
||||
>{{ errors.first('email') }}</small>
|
||||
<small
|
||||
v-for="error in emailErrors"
|
||||
:key="error"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="email-remote-errors"
|
||||
>{{ error }}</small>
|
||||
</div>
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<label for="licenseKey" class="skillboxform-input__label">Lizenz</label>
|
||||
<input
|
||||
id="licenseKey"
|
||||
name="licenseKey"
|
||||
type="text"
|
||||
v-model="licenseKey"
|
||||
data-vv-as="Lizenz"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('licenseKey') }"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="licenseKey-input"
|
||||
/>
|
||||
<small
|
||||
v-if="errors.has('licenseKey') && submitted"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="licenseKey-local-errors"
|
||||
>{{ errors.first('licenseKey') }}</small>
|
||||
<small
|
||||
v-for="error in licenseKeyErrors"
|
||||
:key="error"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="licenseKey-remote-errors"
|
||||
>{{ error }}</small>
|
||||
</div>
|
||||
<div class="skillboxform-input">
|
||||
<small class="skillboxform-input__error" data-cy="registration-error" v-if="registrationError">{{registrationError}}</small>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="button button--primary button--big actions__submit" data-cy="register-button">Jetzt registration</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import REGISTRATION_MUTATION from '@/graphql/gql/mutations/registration.gql';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
|
||||
methods: {
|
||||
validateBeforeSubmit() {
|
||||
this.$validator.validate().then(result => {
|
||||
this.submitted = true;
|
||||
let that = this;
|
||||
if (result) {
|
||||
this.$apollo.mutate({
|
||||
client: 'publicClient',
|
||||
mutation: REGISTRATION_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
firstnameInput: this.firstname,
|
||||
lastnameInput: this.lastname,
|
||||
emailInput: this.email,
|
||||
licenseKeyInput: this.licenseKey,
|
||||
}
|
||||
},
|
||||
update(
|
||||
store,
|
||||
{
|
||||
data: {
|
||||
registration: { success, errors }
|
||||
}
|
||||
}
|
||||
) {
|
||||
try {
|
||||
if (success) {
|
||||
window.location.href = '/registration/set-password/done/';
|
||||
} else {
|
||||
errors.forEach(function(error) {
|
||||
switch (error.field) {
|
||||
case 'email':
|
||||
that.emailErrors = ['Die angegebene E-Mail ist bereits registriert.'];
|
||||
break;
|
||||
case 'license_key':
|
||||
that.licenseKeyErrors = ['Die angegebenen Lizenz ist unglültig'];
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
that.registrationError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
resetForm() {
|
||||
this.email = '';
|
||||
this.lastname = '';
|
||||
this.firstname = '';
|
||||
this.licenseKey = '';
|
||||
this.firstnameErrors = '';
|
||||
this.lastnameErrors = '';
|
||||
this.emailErrors = '';
|
||||
this.licenseKeyErrors = '';
|
||||
this.registrationError = '';
|
||||
this.submitted = false;
|
||||
this.$validator.reset();
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
email: '',
|
||||
lastname: '',
|
||||
firstname: '',
|
||||
licenseKey: '',
|
||||
firstnameErrors: '',
|
||||
lastnameErrors: '',
|
||||
emailErrors: '',
|
||||
licenseKeyErrors: '',
|
||||
registrationError: '',
|
||||
submitted: false
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.text-link {
|
||||
font-family: $sans-serif-font-family;
|
||||
color: $color-brand;
|
||||
}
|
||||
|
||||
.actions {
|
||||
&__reset {
|
||||
display: inline-block;
|
||||
margin-left: $large-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
.registration {
|
||||
&__text {
|
||||
font-family: $sans-serif-font-family;
|
||||
margin-bottom: $small-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<template>
|
||||
<div class="no-class public-page">
|
||||
<h1 class="public-page__title">Sie sind keiner Klasse zugeteilt</h1>
|
||||
<p>Sie können mySkillbox nur verwenden wenn Sie in einer Klasse zugeteilt sind. Aktuell kann Sie nur der mySkillbox-Support einer Klasse zuteilen.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -28,6 +28,8 @@ import surveyPage from '@/pages/survey'
|
|||
import styleGuidePage from '@/pages/styleguide'
|
||||
import moduleRoom from '@/pages/moduleRoom'
|
||||
import login from '@/pages/login'
|
||||
import registration from '@/pages/registration'
|
||||
import waitForClass from '@/pages/waitForClass'
|
||||
|
||||
import store from '@/store/index';
|
||||
|
||||
|
|
@ -117,6 +119,21 @@ const routes = [
|
|||
props: true,
|
||||
meta: {layout: 'simple'}
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: registration,
|
||||
name: 'registration',
|
||||
meta: {
|
||||
public: true,
|
||||
layout: 'public',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/no-class',
|
||||
component: waitForClass,
|
||||
name: 'noClass',
|
||||
meta: {layout: 'public'}
|
||||
},
|
||||
{path: '/styleguide', component: styleGuidePage},
|
||||
{path: '*', component: p404}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
.public-page {
|
||||
&__title {
|
||||
margin-top: 48px;
|
||||
font-size: 2.75rem; // 44px
|
||||
margin-bottom: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,3 +19,4 @@
|
|||
@import "visibility";
|
||||
@import "solutions";
|
||||
@import "password_forms";
|
||||
@import "public-page";
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from rooms.mutations import RoomMutations
|
|||
from rooms.schema import RoomsQuery, ModuleRoomsQuery
|
||||
from users.schema import AllUsersQuery, UsersQuery
|
||||
from users.mutations import ProfileMutations
|
||||
from registration.mutations_public import RegistrationMutations
|
||||
|
||||
|
||||
class Query(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery,
|
||||
|
|
@ -34,7 +35,7 @@ class Query(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQ
|
|||
|
||||
|
||||
class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations,
|
||||
ProfileMutations, SurveyMutations, NoteMutations, graphene.ObjectType):
|
||||
ProfileMutations, SurveyMutations, NoteMutations, RegistrationMutations, graphene.ObjectType):
|
||||
|
||||
if settings.DEBUG:
|
||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ from django.conf import settings
|
|||
from graphene_django.debug import DjangoDebug
|
||||
|
||||
from users.mutations_public import UserMutations
|
||||
from registration.mutations_public import RegistrationMutations
|
||||
|
||||
|
||||
class Mutation(UserMutations, graphene.ObjectType):
|
||||
class Mutation(UserMutations, RegistrationMutations, graphene.ObjectType):
|
||||
|
||||
if settings.DEBUG:
|
||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from django.core.management import BaseCommand
|
|||
from django.db import connection
|
||||
from wagtail.core.models import Page
|
||||
|
||||
from assignments.factories import AssignmentFactory
|
||||
from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory
|
||||
from core.factories import UserFactory
|
||||
from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory
|
||||
|
|
@ -103,5 +102,10 @@ class Command(BaseCommand):
|
|||
# ContentBlockFactory.create(parent=chapter, **self.filter_data(content_block_data, 'contents'))
|
||||
ContentBlockFactory.create(parent=chapter, module=module, **content_block_data)
|
||||
|
||||
|
||||
|
||||
# now create all and rooms
|
||||
management.call_command('dummy_rooms', verbosity=0)
|
||||
|
||||
# create license
|
||||
management.call_command('create_dummy_license', verbosity=0)
|
||||
|
|
|
|||
|
|
@ -30,11 +30,7 @@ class Command(BaseCommand):
|
|||
|
||||
self.stdout.write("Creating user {} {}, {}".format(first_name, last_name, email))
|
||||
|
||||
user, created = User.objects.get_or_create(email=email, username=email)
|
||||
user.first_name = first_name
|
||||
user.last_name = last_name
|
||||
user.set_password(User.objects.make_random_password())
|
||||
user.save()
|
||||
user = User.objects.create_user_with_random_password(first_name, last_name, email)
|
||||
|
||||
if row['Rolle'] == 'Lehrer':
|
||||
self.stdout.write("Assigning teacher role")
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ INSTALLED_APPS = [
|
|||
'statistics',
|
||||
'surveys',
|
||||
'notes',
|
||||
'registration',
|
||||
|
||||
'wagtail.contrib.forms',
|
||||
'wagtail.contrib.redirects',
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ $base-font-size: 15px;
|
|||
|
||||
$space: 10px;
|
||||
|
||||
$color-brand: #17A887;
|
||||
|
||||
body {
|
||||
font-family: $font-family;
|
||||
font-size: $base-font-size;
|
||||
|
|
@ -115,6 +117,15 @@ input[type=text], input[type=password], input[type=email], select {
|
|||
|
||||
.reset__text {
|
||||
margin-bottom: 52px;
|
||||
line-height: 1.5rem;
|
||||
font-size: 1.125rem;
|
||||
|
||||
a {
|
||||
color: $color-brand;
|
||||
font-family: 'Montserrat', Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
.reset__form label {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
{% block body %}
|
||||
<div class="reset">
|
||||
<h2 class="reset__heading">{% trans 'Setzen Sie Ihr neues Passwort' %}</h2>
|
||||
<p class="reset__text">{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
|
||||
<p class="reset__text">{% trans 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten' %}</p>
|
||||
<form method="post" class="mt-1 reset__form">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Sie haben es geschafft' %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="reset">
|
||||
<h2 class="reset__heading">{% trans 'Sie haben es geschafft' %}</h2>
|
||||
<p class="reset__text">{% trans 'Ihr Passwort wurde erfolgreich gespeichert. Sie können sich nun anmelden.' %}</p>
|
||||
<p class="reset__text"><a href="/login">{% trans 'Jetzt anmelden' %}</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!-- templates/registration/password_reset_confirm.html -->
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="reset">
|
||||
<h2 class="reset__heading">{% trans 'Geben Sie ein persönliches Passwort ein:' %}</h2>
|
||||
<p class="reset__text">{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
|
||||
<form method="post" class="mt-1 reset__form">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" name="action">{% trans 'Passwort speichern' %}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<!-- templates/registration/password_reset_form.html -->
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Schauen Sie in Ihr Postfach' %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="reset">
|
||||
<h2 class="reset__heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2>
|
||||
<p class="reset__text">{% trans 'Wir haben ein E-Mail mit allen weiteren Anweisungen an Sie verschickt. Die E-Mail sollte in Kürze bei Ihnen ankommen.' %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}Sie erhalten diese E-Mail, um Ihr Passwort auf mySkillbox initial zu setzen.{% endblocktrans %}
|
||||
|
||||
{% trans "Bitte öffnen Sie folgende Seite, um Ihr neues Passwort einzugeben:" %}
|
||||
{% block reset_link %}
|
||||
{{ protocol }}://{{ domain }}{% url 'set_password_confirm' uidb64=uid token=token %}
|
||||
{% endblock %}
|
||||
{% trans "Ihr Benutzername lautet:" %} {{ user.get_username }}
|
||||
|
||||
{% trans "Ihr mySkillbox Team" %}
|
||||
|
||||
{% endautoescape %}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<!-- templates/registration/password_reset_form.html -->
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Willkommen bei mySkillbox' %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="reset">
|
||||
<h2 class="reset__heading">{% trans 'Willkommen bei Myskillbox' %}</h2>
|
||||
<p class="reset__text">{% trans 'Bevor Sie mySkillbox verwenden können, müssen Sie Ihre E-Mail-Adresse bestätigen und ein persönliches Passwort festlegen.' %}</p>
|
||||
<form method="post" class="mt-1 reset__form">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
<label for="id_email">{% trans 'Geben Sie als erstes hier Ihre E-Mail-Adresse ein:' %}</label>
|
||||
{{ form.email }}
|
||||
</div>
|
||||
<button class="btn mt-1" type="submit" name="action">{% trans 'E-Mail bestätigen' %}</button>
|
||||
<input type="hidden" name="next" value="{{ next }}"/>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Myskillbox: E-Mail bestätigen und Passwort setzen
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
{% block body %}
|
||||
<div class="reset">
|
||||
<h2 class="reset__heading">{% trans 'Sie haben es geschafft' %}</h2>
|
||||
<p class="reset__text">% trans 'Ihr Passwort wurde erfolgreich gespeichert. Sie können sich nun anmelden.' %}</p>
|
||||
<p class="reset__text">{% trans 'Ihr Passwort wurde erfolgreich gespeichert. Sie können sich nun anmelden.' %}</p>
|
||||
<p class="reset__text"><a href="/login">{% trans 'Jetzt anmelden' %}</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
{% block body %}
|
||||
<div class="reset">
|
||||
<h2 class="reset__heading">{% trans 'Geben Sie ein persönliches Passwort ein:' %}</h2>
|
||||
<p class="reset__text">{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
|
||||
<p class="reset__text">{% trans 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten.' %}</p>
|
||||
<form method="post" class="mt-1 reset__form">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}Sie erhalten diese E-Mail, um Ihr Passwort auf {{ site_name }} initial zu setzen.{% endblocktrans %}
|
||||
{% blocktrans %}Sie erhalten diese E-Mail, um Ihr Passwort auf mySkillbox initial zu setzen.{% endblocktrans %}
|
||||
|
||||
{% trans "Bitte öffnen Sie folgende Seite, um Ihr neues Passwort einzugeben:" %}
|
||||
{% block reset_link %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import json
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from core import settings
|
||||
from core.factories import UserFactory
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ from wagtail.admin import urls as wagtailadmin_urls
|
|||
from wagtail.core import urls as wagtail_urls
|
||||
|
||||
from core import views
|
||||
from core.views import SetPasswordView, SetPasswordDoneView, SetPasswordConfirmView, SetPasswordCompleteView
|
||||
from core.views import LegacySetPasswordView, LegacySetPasswordDoneView, LegacySetPasswordConfirmView,\
|
||||
LegacySetPasswordCompleteView, SetPasswordView, SetPasswordDoneView, SetPasswordConfirmView, SetPasswordCompleteView
|
||||
|
||||
urlpatterns = [
|
||||
# django admin
|
||||
|
|
@ -16,11 +17,20 @@ urlpatterns = [
|
|||
url(r'^accounts/', include('django.contrib.auth.urls')),
|
||||
url(r'^statistics/', include('statistics.urls', namespace='statistics')),
|
||||
|
||||
# legacy - will be removed
|
||||
# set password
|
||||
path('welcome/', SetPasswordView.as_view(), name='set_password'),
|
||||
path('set-password/done/', SetPasswordDoneView.as_view(), name='set_password_done'),
|
||||
path('set-password/<uidb64>/<token>/', SetPasswordConfirmView.as_view(), name='set_password_confirm'),
|
||||
path('set-password/complete/', SetPasswordCompleteView.as_view(), name='set_password_complete'),
|
||||
path('welcome/', LegacySetPasswordView.as_view(), name='set_password'),
|
||||
path('set-password/done/', LegacySetPasswordDoneView.as_view(), name='set_password_done'),
|
||||
path('set-password/<uidb64>/<token>/', LegacySetPasswordConfirmView.as_view(), name='set_password_confirm'),
|
||||
path('set-password/complete/', LegacySetPasswordCompleteView.as_view(), name='set_password_complete'),
|
||||
|
||||
# set password upon registration
|
||||
path('registration/welcome/', SetPasswordView.as_view(), name='registration_set_password'),
|
||||
path('registration/set-password/done/', SetPasswordDoneView.as_view(), name='registration_set_password_done'),
|
||||
path('registration/set-password/<uidb64>/<token>/', SetPasswordConfirmView.as_view(),
|
||||
name='registration_set_password_confirm'),
|
||||
path('registration/set-password/complete/', SetPasswordCompleteView.as_view(),
|
||||
name='registration_set_password_complete'),
|
||||
|
||||
# wagtail
|
||||
url(r'^cms/', include(wagtailadmin_urls)),
|
||||
|
|
|
|||
|
|
@ -27,6 +27,31 @@ def home(request):
|
|||
|
||||
|
||||
class SetPasswordView(PasswordResetView):
|
||||
email_template_name = 'registration/registration_set_password_email.html'
|
||||
subject_template_name = 'registration/registration_set_password_subject.txt'
|
||||
success_url = reverse_lazy('registration_set_password_done')
|
||||
template_name = 'registration/registration_set_password_form.html'
|
||||
title = _('Password setzen')
|
||||
|
||||
|
||||
class SetPasswordDoneView(PasswordResetDoneView):
|
||||
template_name = 'registration/registration_set_password_done.html'
|
||||
title = _('Password setzen versandt')
|
||||
|
||||
|
||||
class SetPasswordConfirmView(PasswordResetConfirmView):
|
||||
success_url = reverse_lazy('registration_set_password_complete')
|
||||
template_name = 'registration/registration_set_password_confirm.html'
|
||||
title = _('Gib ein Passwort ein')
|
||||
|
||||
|
||||
class SetPasswordCompleteView(PasswordResetCompleteView):
|
||||
template_name = 'registration/registration_set_password_complete.html'
|
||||
title = _('Passwort setzen erfolgreich')
|
||||
|
||||
|
||||
# legacy
|
||||
class LegacySetPasswordView(PasswordResetView):
|
||||
email_template_name = 'registration/set_password_email.html'
|
||||
subject_template_name = 'registration/set_password_subject.txt'
|
||||
success_url = reverse_lazy('set_password_done')
|
||||
|
|
@ -34,17 +59,17 @@ class SetPasswordView(PasswordResetView):
|
|||
title = _('Password setzen')
|
||||
|
||||
|
||||
class SetPasswordDoneView(PasswordResetDoneView):
|
||||
class LegacySetPasswordDoneView(PasswordResetDoneView):
|
||||
template_name = 'registration/set_password_done.html'
|
||||
title = _('Password setzen versandt')
|
||||
|
||||
|
||||
class SetPasswordConfirmView(PasswordResetConfirmView):
|
||||
class LegacySetPasswordConfirmView(PasswordResetConfirmView):
|
||||
success_url = reverse_lazy('set_password_complete')
|
||||
template_name = 'registration/set_password_confirm.html'
|
||||
title = _('Gib ein Passwort ein')
|
||||
|
||||
|
||||
class SetPasswordCompleteView(PasswordResetCompleteView):
|
||||
class LegacySetPasswordCompleteView(PasswordResetCompleteView):
|
||||
template_name = 'registration/set_password_complete.html'
|
||||
title = _('Passwort setzen erfolgreich')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-10
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.contrib import admin
|
||||
|
||||
from registration.models import LicenseType, License
|
||||
|
||||
|
||||
@admin.register(LicenseType)
|
||||
class LicenseTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'name', 'key', 'for_role', 'active')
|
||||
list_filter = ('for_role', 'active')
|
||||
|
||||
|
||||
@admin.register(License)
|
||||
class LicenseAdmin(admin.ModelAdmin):
|
||||
list_display = ('license_type', 'licensee')
|
||||
list_filter = ('license_type', 'licensee')
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UserConfig(AppConfig):
|
||||
name = 'registration'
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
import random
|
||||
|
||||
import factory
|
||||
|
||||
from registration.models import LicenseType, License
|
||||
|
||||
|
||||
class LicenseTypeFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = LicenseType
|
||||
|
||||
name = factory.Sequence(lambda n: 'license-{}'.format(n))
|
||||
active = True
|
||||
key = factory.Sequence(lambda n: "license-key-%03d" % n)
|
||||
description = factory.Sequence(lambda n: "Some description %03d" % n)
|
||||
|
||||
|
||||
class LicenseFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = License
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-23
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.conf import settings
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-23
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.conf import settings
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-23
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from registration.models import LicenseType
|
||||
from users.models import Role
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
try:
|
||||
role = Role.objects.get(key=Role.objects.TEACHER_KEY)
|
||||
except Role.DoesNotExist:
|
||||
print("LicenseType requires that a Teacher Role exsits")
|
||||
|
||||
LicenseType.objects.create(name='dummy_license',
|
||||
for_role=role,
|
||||
active=True,
|
||||
key='c1fa2e2a-2e27-480d-8469-2e88414c4ad8',
|
||||
description='dummy license')
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Generated by Django 2.0.6 on 2019-10-09 09:05
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('users', '0009_auto_20191009_0905'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='License',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LicenseType',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='License name')),
|
||||
('key', models.CharField(max_length=128)),
|
||||
('active', models.BooleanField(default=False, verbose_name='License active')),
|
||||
('description', models.TextField(default='', verbose_name='Description')),
|
||||
('for_role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Role')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='license',
|
||||
name='license_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='registration.LicenseType'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='license',
|
||||
name='licensee',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.0.6 on 2019-10-10 09:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('registration', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='licensetype',
|
||||
name='key',
|
||||
field=models.CharField(max_length=128, unique=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db import models
|
||||
|
||||
from users.managers import RoleManager
|
||||
from users.models import Role, User
|
||||
|
||||
|
||||
class LicenseType(models.Model):
|
||||
|
||||
name = models.CharField(_('License name'), max_length=255, blank=False, null=False)
|
||||
for_role = models.ForeignKey(Role, blank=False, null=False, on_delete=models.CASCADE)
|
||||
key = models.CharField(max_length=128, blank=False, null=False, unique=True)
|
||||
active = models.BooleanField(_('License active'), default=False)
|
||||
description = models.TextField(_('Description'), default="")
|
||||
|
||||
def is_teacher_license(self):
|
||||
return self.for_role.key == RoleManager.TEACHER_KEY
|
||||
|
||||
def __str__(self):
|
||||
return '%s - role: %s' % (self.name, self.for_role)
|
||||
|
||||
|
||||
class License(models.Model):
|
||||
license_type = models.ForeignKey(LicenseType, blank=False, null=False, on_delete=models.CASCADE)
|
||||
licensee = models.ForeignKey(User, blank=False, null=True, on_delete=models.CASCADE)
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
import graphene
|
||||
from graphene import relay
|
||||
|
||||
from core.views import SetPasswordView
|
||||
from registration.models import License
|
||||
from registration.serializers import RegistrationSerializer
|
||||
from users.models import User, Role, UserRole, SchoolClass
|
||||
|
||||
|
||||
class PublicFieldError(graphene.ObjectType):
|
||||
code = graphene.String()
|
||||
|
||||
|
||||
class MutationError(graphene.ObjectType):
|
||||
field = graphene.String()
|
||||
errors = graphene.List(PublicFieldError)
|
||||
|
||||
|
||||
class Registration(relay.ClientIDMutation):
|
||||
class Input:
|
||||
firstname_input = graphene.String()
|
||||
lastname_input = graphene.String()
|
||||
email_input = graphene.String()
|
||||
license_key_input = graphene.String()
|
||||
|
||||
success = graphene.Boolean()
|
||||
errors = graphene.List(MutationError) # todo: change for consistency
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
first_name = kwargs.get('firstname_input')
|
||||
last_name = kwargs.get('lastname_input')
|
||||
email = kwargs.get('email_input')
|
||||
license_key = kwargs.get('license_key_input')
|
||||
registration_data = {
|
||||
'first_name': first_name,
|
||||
'last_name': last_name,
|
||||
'email': email,
|
||||
'license_key': license_key,
|
||||
}
|
||||
|
||||
serializer = RegistrationSerializer(data=registration_data)
|
||||
|
||||
if serializer.is_valid():
|
||||
user = User.objects.create_user_with_random_password(serializer.data['first_name'],
|
||||
serializer.data['last_name'],
|
||||
serializer.data['email'])
|
||||
sb_license = License.objects.create(licensee=user, license_type=serializer.context['license_type'])
|
||||
|
||||
if sb_license.license_type.is_teacher_license():
|
||||
teacher_role = Role.objects.get(key=Role.objects.TEACHER_KEY)
|
||||
UserRole.objects.get_or_create(user=user, role=teacher_role)
|
||||
default_class_name = SchoolClass.generate_default_group_name()
|
||||
default_class = SchoolClass.objects.create(name=default_class_name)
|
||||
user.school_classes.add(default_class)
|
||||
else:
|
||||
student_role = Role.objects.get(key=Role.objects.STUDENT_KEY)
|
||||
UserRole.objects.get_or_create(user=user, role=student_role)
|
||||
|
||||
password_reset_view = SetPasswordView()
|
||||
password_reset_view.request = info.context
|
||||
form = password_reset_view.form_class({'email': user.email})
|
||||
|
||||
if not form.is_valid():
|
||||
return cls(success=False, errors=form.errors)
|
||||
|
||||
password_reset_view.form_valid(form)
|
||||
|
||||
return cls(success=True)
|
||||
|
||||
errors = []
|
||||
for key, value in serializer.errors.items():
|
||||
error = MutationError(field=key, errors=[])
|
||||
for field_error in serializer.errors[key]:
|
||||
error.errors.append(PublicFieldError(code=field_error.code))
|
||||
|
||||
errors.append(error)
|
||||
|
||||
return cls(success=False, errors=errors)
|
||||
|
||||
|
||||
class RegistrationMutations:
|
||||
registration = Registration.Field()
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import CharField, EmailField
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from registration.models import License, LicenseType
|
||||
|
||||
|
||||
class RegistrationSerializer(serializers.Serializer):
|
||||
first_name = CharField(allow_blank=False)
|
||||
last_name = CharField(allow_blank=False)
|
||||
email = EmailField(allow_blank=False)
|
||||
license_key = CharField(allow_blank=False)
|
||||
skillbox_license = None
|
||||
|
||||
def validate_email(self, value):
|
||||
lower_email = value.lower()
|
||||
# the email is used as username
|
||||
if len(get_user_model().objects.filter(username=lower_email)) > 0:
|
||||
raise serializers.ValidationError(_(u'Diese E-Mail ist bereits registriert'))
|
||||
elif len(get_user_model().objects.filter(email=lower_email)) > 0:
|
||||
raise serializers.ValidationError(_(u'Dieser E-Mail ist bereits registriert'))
|
||||
else:
|
||||
return lower_email
|
||||
|
||||
def validate_license_key(self, value):
|
||||
license_types = LicenseType.objects.filter(key=value, active=True)
|
||||
if len(license_types) == 0:
|
||||
raise serializers.ValidationError(_(u'Die Lizenznummer ist ungültig'))
|
||||
|
||||
self.context['license_type'] = license_types[0] # Assuming there is just ONE license per key
|
||||
return value
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.conf import settings
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.core import mail
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.test import TestCase, RequestFactory
|
||||
from graphene.test import Client
|
||||
|
||||
from api.schema import schema
|
||||
from registration.factories import LicenseTypeFactory, LicenseFactory
|
||||
from registration.models import License
|
||||
from users.managers import RoleManager
|
||||
from users.models import Role, User, UserRole, SchoolClass
|
||||
|
||||
|
||||
class RegistrationTests(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
self.teacher_role = Role.objects.create(key=Role.objects.TEACHER_KEY, name="Teacher Role")
|
||||
self.student_role = Role.objects.create(key=Role.objects.STUDENT_KEY, name="Student Role")
|
||||
|
||||
self.teacher_license_type = LicenseTypeFactory(for_role=self.teacher_role)
|
||||
self.student_license_type = LicenseTypeFactory(for_role=self.student_role)
|
||||
|
||||
self.teacher_license = LicenseFactory(license_type=self.teacher_license_type)
|
||||
self.student_license = LicenseFactory(license_type=self.student_license_type)
|
||||
|
||||
request = RequestFactory().post('/')
|
||||
|
||||
self.email = 'sepp@skillbox.iterativ.ch'
|
||||
self.first_name = 'Sepp'
|
||||
self.last_name = 'Feuz'
|
||||
|
||||
# adding session
|
||||
middleware = SessionMiddleware()
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
self.client = Client(schema=schema, context_value=request)
|
||||
|
||||
def make_register_mutation(self, first_name, last_name, email, license_key):
|
||||
mutation = '''
|
||||
mutation Registration($input: RegistrationInput!){
|
||||
registration(input: $input) {
|
||||
success
|
||||
errors {
|
||||
field
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
return self.client.execute(mutation, variables={
|
||||
'input': {
|
||||
'firstnameInput': first_name,
|
||||
'lastnameInput': last_name,
|
||||
'emailInput': email,
|
||||
'licenseKeyInput': license_key,
|
||||
}
|
||||
})
|
||||
|
||||
def _assert_user_registration(self, count, email, role_key):
|
||||
users = User.objects.filter(username=self.email)
|
||||
self.assertEqual(len(users), count)
|
||||
user_roles = UserRole.objects.filter(user__email=email, role__key=role_key)
|
||||
self.assertEqual(len(user_roles), count)
|
||||
licenses = License.objects.filter(licensee__email=email, license_type__for_role__key=role_key)
|
||||
self.assertEqual(len(licenses), count)
|
||||
|
||||
def test_user_can_register_as_teacher(self):
|
||||
self._assert_user_registration(0, self.email, RoleManager.TEACHER_KEY)
|
||||
school_classes = SchoolClass.objects.filter(name__startswith='Meine Klasse')
|
||||
self.assertEqual(len(school_classes), 0)
|
||||
result = self.make_register_mutation(self.first_name, self.last_name, self.email, self.teacher_license_type.key)
|
||||
self.assertTrue(result.get('data').get('registration').get('success'))
|
||||
self._assert_user_registration(1, self.email, RoleManager.TEACHER_KEY)
|
||||
school_classes = SchoolClass.objects.filter(name__startswith='Meine Klasse')
|
||||
self.assertEqual(len(school_classes), 1)
|
||||
user = User.objects.get(email=self.email)
|
||||
self.assertTrue(school_classes[0].is_user_in_schoolclass(user))
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, 'Myskillbox: E-Mail bestätigen und Passwort setzen')
|
||||
|
||||
def test_user_can_register_as_student(self):
|
||||
self._assert_user_registration(0, self.email, RoleManager.STUDENT_KEY)
|
||||
result = self.make_register_mutation(self.first_name, self.last_name, self.email, self.student_license_type.key)
|
||||
self.assertTrue(result.get('data').get('registration').get('success'))
|
||||
self._assert_user_registration(1, self.email, RoleManager.STUDENT_KEY)
|
||||
|
||||
def test_existing_user_cannot_register(self):
|
||||
self._assert_user_registration(0, self.email, RoleManager.STUDENT_KEY)
|
||||
self.make_register_mutation(self.first_name, self.last_name, self.email, self.student_license_type.key)
|
||||
result = self.make_register_mutation(self.first_name, self.last_name, self.email, self.student_license_type.key)
|
||||
self.assertEqual(result.get('data').get('registration').get('errors')[0].get('field'), 'email')
|
||||
|
||||
def test_existing_user_cannot_register_with_uppercase_email(self):
|
||||
self._assert_user_registration(0, self.email, RoleManager.STUDENT_KEY)
|
||||
self.make_register_mutation(self.first_name, self.last_name, self.email.upper(), self.student_license_type.key)
|
||||
result = self.make_register_mutation(self.first_name, self.last_name, self.email, self.student_license_type.key)
|
||||
self.assertEqual(result.get('data').get('registration').get('errors')[0].get('field'), 'email')
|
||||
|
||||
def test_user_cannot_register_if_firstname_is_missing(self):
|
||||
result = self.make_register_mutation('', self.last_name, self.email, self.teacher_license_type.key)
|
||||
self.assertEqual(result.get('data').get('registration').get('errors')[0].get('field'), 'first_name')
|
||||
self.assertFalse(result.get('data').get('registration').get('success'))
|
||||
|
||||
def test_user_cannot_register_if_lastname_is_missing(self):
|
||||
result = self.make_register_mutation(self.first_name, '', self.email, self.teacher_license_type.key)
|
||||
self.assertEqual(result.get('data').get('registration').get('errors')[0].get('field'), 'last_name')
|
||||
self.assertFalse(result.get('data').get('registration').get('success'))
|
||||
|
||||
def test_user_cannot_register_if_email_is_missing(self):
|
||||
result = self.make_register_mutation(self.first_name, self.last_name, '', self.teacher_license_type.key)
|
||||
self.assertEqual(result.get('data').get('registration').get('errors')[0].get('field'), 'email')
|
||||
self.assertFalse(result.get('data').get('registration').get('success'))
|
||||
|
|
@ -2,6 +2,7 @@ from django.contrib.auth.models import Permission
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import UserManager as DjangoUserManager
|
||||
|
||||
|
||||
class RoleManager(models.Manager):
|
||||
|
|
@ -78,3 +79,13 @@ class UserRoleManager(models.Manager):
|
|||
user_role = self.model(user=user, role=role)
|
||||
user_role.save()
|
||||
return user_role
|
||||
|
||||
|
||||
class UserManager(DjangoUserManager):
|
||||
def create_user_with_random_password(self, first_name, last_name, email):
|
||||
user, created = self.model.objects.get_or_create(email=email, username=email)
|
||||
user.first_name = first_name
|
||||
user.last_name = last_name
|
||||
user.set_password(self.model.objects.make_random_password())
|
||||
user.save()
|
||||
return user
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 2.0.6 on 2019-10-09 09:05
|
||||
|
||||
from django.db import migrations
|
||||
import users.managers
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0008_auto_20190904_1410'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelManagers(
|
||||
name='user',
|
||||
managers=[
|
||||
('objects', users.managers.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
import re
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AbstractUser, Permission
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from users.managers import RoleManager, UserRoleManager
|
||||
from users.managers import RoleManager, UserRoleManager, UserManager
|
||||
|
||||
DEFAULT_SCHOOL_ID = 1
|
||||
|
||||
|
|
@ -14,6 +16,8 @@ class User(AbstractUser):
|
|||
avatar_url = models.CharField(max_length=254, blank=True, default='')
|
||||
email = models.EmailField(_('email address'), unique=True)
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
def get_role_permissions(self):
|
||||
perms = set()
|
||||
for role in Role.objects.get_roles_for_user(self):
|
||||
|
|
@ -70,6 +74,25 @@ class SchoolClass(models.Model):
|
|||
def __str__(self):
|
||||
return 'SchoolClass {}-{}'.format(self.id, self.name)
|
||||
|
||||
@classmethod
|
||||
def generate_default_group_name(cls):
|
||||
prefix = 'Meine Klasse'
|
||||
prefix_regex = r'Meine Klasse (\d+)'
|
||||
initial_default_group = '{} 1'.format(prefix)
|
||||
my_group_filter = cls.objects.filter(name__startswith=prefix).order_by('-pk')
|
||||
|
||||
if len(my_group_filter) == 0:
|
||||
return initial_default_group
|
||||
|
||||
match = re.search(prefix_regex, my_group_filter[0].name)
|
||||
|
||||
if not match:
|
||||
return initial_default_group
|
||||
|
||||
index = int(match.group(1))
|
||||
|
||||
return '{} {}'.format(prefix, index + 1)
|
||||
|
||||
def is_user_in_schoolclass(self, user):
|
||||
return user.is_superuser or user.school_classes.filter(pk=self.id).count() > 0
|
||||
|
||||
|
|
|
|||
|
|
@ -7,20 +7,16 @@
|
|||
#
|
||||
# Created on 2019-10-01
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
import re
|
||||
|
||||
import graphene
|
||||
from django.contrib.auth import authenticate, login
|
||||
from graphene import relay
|
||||
|
||||
|
||||
class FieldError(graphene.ObjectType):
|
||||
code = graphene.String()
|
||||
from registration.models import License
|
||||
|
||||
|
||||
class MutationError(graphene.ObjectType):
|
||||
class LoginError(graphene.ObjectType):
|
||||
field = graphene.String()
|
||||
errors = graphene.List(FieldError)
|
||||
|
||||
|
||||
class Login(relay.ClientIDMutation):
|
||||
|
|
@ -29,17 +25,30 @@ class Login(relay.ClientIDMutation):
|
|||
password_input = graphene.String()
|
||||
|
||||
success = graphene.Boolean()
|
||||
errors = graphene.List(MutationError) # todo: change for consistency
|
||||
errors = graphene.List(LoginError) # todo: change for consistency
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
|
||||
user = authenticate(username=kwargs.get('username_input'), password=kwargs.get('password_input'))
|
||||
if user is not None:
|
||||
login(info.context, user)
|
||||
return cls(success=True, errors=[])
|
||||
else:
|
||||
return cls(success=False, errors=['invalid_credentials'])
|
||||
if user is None:
|
||||
error = LoginError(field='invalid_credentials')
|
||||
return cls(success=False, errors=[error])
|
||||
|
||||
user_license = None
|
||||
|
||||
try:
|
||||
user_license = License.objects.get(licensee=user)
|
||||
except License.DoesNotExist:
|
||||
# current users have no license, allow them to login
|
||||
pass
|
||||
|
||||
if user_license is not None and not user_license.license_type.active:
|
||||
error = LoginError(field='license_inactive')
|
||||
return cls(success=False, errors=[error])
|
||||
|
||||
login(info.context, user)
|
||||
return cls(success=True, errors=[])
|
||||
|
||||
|
||||
class UserMutations:
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ def validate_old_new_password(value):
|
|||
return value
|
||||
|
||||
|
||||
def validate_strong_email(password):
|
||||
def validate_strong_password(password):
|
||||
|
||||
has_number = re.search('\d', password)
|
||||
has_upper = re.search('[A-Z]', password)
|
||||
|
|
@ -56,7 +56,7 @@ class PasswordSerialzer(serializers.Serializer):
|
|||
new_password = CharField(allow_blank=True, min_length=MIN_PASSWORD_LENGTH)
|
||||
|
||||
def validate_new_password(self, value):
|
||||
return validate_strong_email(value)
|
||||
return validate_strong_password(value)
|
||||
|
||||
def validate_old_password(self, value):
|
||||
return validate_old_password(value, self.context.username)
|
||||
|
|
|
|||
|
|
@ -13,11 +13,15 @@ from graphene.test import Client
|
|||
|
||||
from api.schema_public import schema
|
||||
from core.factories import UserFactory
|
||||
from registration.factories import LicenseTypeFactory, LicenseFactory
|
||||
from users.models import Role
|
||||
|
||||
|
||||
class PasswordResetTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = UserFactory(username='aschi@iterativ.ch', email='aschi@iterativ.ch')
|
||||
self.teacher_role = Role.objects.create(key=Role.objects.TEACHER_KEY, name="Teacher Role")
|
||||
self.teacher_license_type = LicenseTypeFactory(for_role=self.teacher_role)
|
||||
|
||||
request = RequestFactory().post('/')
|
||||
|
||||
|
|
@ -62,3 +66,25 @@ class PasswordResetTests(TestCase):
|
|||
|
||||
result = self.make_login_mutation(self.user.email, 'test1234')
|
||||
self.assertFalse(result.get('data').get('login').get('success'))
|
||||
|
||||
def test_user_with_active_license_can_login(self):
|
||||
password = 'test123'
|
||||
self.user.set_password(password)
|
||||
self.user.save()
|
||||
|
||||
LicenseFactory(license_type=self.teacher_license_type, licensee=self.user)
|
||||
|
||||
result = self.make_login_mutation(self.user.email, password)
|
||||
self.assertTrue(result.get('data').get('login').get('success'))
|
||||
|
||||
def test_user_with_inactive_license_cannot_login(self):
|
||||
password = 'test123'
|
||||
self.user.set_password(password)
|
||||
self.user.save()
|
||||
|
||||
self.teacher_license_type.active = False
|
||||
self.teacher_license_type.save()
|
||||
LicenseFactory(license_type=self.teacher_license_type, licensee=self.user)
|
||||
|
||||
result = self.make_login_mutation(self.user.email, password)
|
||||
self.assertFalse(result.get('data').get('login').get('success'))
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from django.contrib.auth import authenticate
|
|||
from users.factories import SchoolClassFactory
|
||||
|
||||
|
||||
class PasswordUpdate(TestCase):
|
||||
class MySchoolClasses(TestCase):
|
||||
def setUp(self):
|
||||
self.user = UserFactory(username='aschi')
|
||||
self.another_user = UserFactory(username='pesche')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-10
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.conf import settings
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-04-09
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.test import TestCase
|
||||
|
||||
from users.models import SchoolClass
|
||||
|
||||
|
||||
class SchoolClasses(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.prefix = 'Meine Klasse'
|
||||
|
||||
def test_default_class_name_initial(self):
|
||||
class_name = SchoolClass.generate_default_group_name()
|
||||
self.assertEqual('{} 1'.format(self.prefix), class_name)
|
||||
|
||||
def test_default_class_name_initial_with_similar_existing(self):
|
||||
SchoolClass.objects.create(name='{} abc212'.format(self.prefix))
|
||||
class_name = SchoolClass.generate_default_group_name()
|
||||
self.assertEqual('{} 1'.format(self.prefix), class_name)
|
||||
|
||||
def test_default_class_name_if_existing(self):
|
||||
SchoolClass.objects.create(name='{} 1'.format(self.prefix))
|
||||
SchoolClass.objects.create(name='{} 10'.format(self.prefix))
|
||||
class_name = SchoolClass.generate_default_group_name()
|
||||
self.assertEqual('{} 11'.format(self.prefix), class_name)
|
||||
Loading…
Reference in New Issue