commit
5f4aefa722
|
|
@ -43,5 +43,3 @@ server/media/
|
||||||
|
|
||||||
.coverage
|
.coverage
|
||||||
|
|
||||||
# Cypress screenshots
|
|
||||||
client/cypress/screenshots
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ aliases:
|
||||||
caches:
|
caches:
|
||||||
- pip
|
- pip
|
||||||
- node
|
- node
|
||||||
|
artifacts:
|
||||||
|
- client/cypress/**/*.png
|
||||||
|
- client/cypress/**/*.mp4
|
||||||
services:
|
services:
|
||||||
- postgres
|
- postgres
|
||||||
script:
|
script:
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,7 @@ cypress/videos
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
|
|
||||||
|
# Cypress
|
||||||
|
cypress/screenshots
|
||||||
|
cypress/videos
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"baseUrl": "http://localhost:8000",
|
"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();
|
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": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
|
|
@ -9004,13 +9003,11 @@
|
||||||
},
|
},
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
|
|
@ -9023,18 +9020,15 @@
|
||||||
},
|
},
|
||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
|
@ -9137,8 +9131,7 @@
|
||||||
},
|
},
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
|
|
@ -9148,7 +9141,6 @@
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -9161,20 +9153,17 @@
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.1",
|
"safe-buffer": "^5.1.1",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
|
|
@ -9191,7 +9180,6 @@
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
|
|
@ -9264,8 +9252,7 @@
|
||||||
},
|
},
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
|
@ -9275,7 +9262,6 @@
|
||||||
"once": {
|
"once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
|
|
@ -9351,8 +9337,7 @@
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"safer-buffer": {
|
"safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
|
|
@ -9382,7 +9367,6 @@
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
|
|
@ -9400,7 +9384,6 @@
|
||||||
"strip-ansi": {
|
"strip-ansi": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -9439,13 +9422,11 @@
|
||||||
},
|
},
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -11449,8 +11430,7 @@
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
|
|
@ -11475,8 +11455,7 @@
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
:class="{ 'skillboxform-input__input--error': errors.has('oldPassword') }"
|
:class="{ 'skillboxform-input__input--error': errors.has('oldPassword') }"
|
||||||
class="change-form__old skillbox-input skillboxform-input__input"
|
class="change-form__old skillbox-input skillboxform-input__input"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
data-vv-as="Altes Passwort"
|
||||||
data-cy="old-password">
|
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-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>
|
<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"
|
name="newPassword"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="newPassword"
|
v-model="newPassword"
|
||||||
|
data-vv-as="Neues Passwort"
|
||||||
v-validate="'required|min:8|strongPassword'"
|
v-validate="'required|min:8|strongPassword'"
|
||||||
:class="{ 'skillboxform-input__input--error': errors.has('newPassword') }"
|
:class="{ 'skillboxform-input__input--error': errors.has('newPassword') }"
|
||||||
class="change-form__new skillbox-input skillboxform-input__input"
|
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 {dateFilter} from './filters/date-filter';
|
||||||
import autoGrow from '@/directives/auto-grow'
|
import autoGrow from '@/directives/auto-grow'
|
||||||
import clickOutside from '@/directives/click-outside'
|
import clickOutside from '@/directives/click-outside'
|
||||||
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
|
@ -98,13 +99,22 @@ Validator.extend('strongPassword', {
|
||||||
return strongRegex.test(value);
|
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, {
|
Vue.use(VeeValidate, {
|
||||||
locale: 'de'
|
locale: 'de'
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.filter('date', dateFilter);
|
Vue.filter('date', dateFilter);
|
||||||
|
|
||||||
/* logged in guard */
|
/* guards */
|
||||||
|
|
||||||
function getCookieValue(cookieName) {
|
function getCookieValue(cookieName) {
|
||||||
// https://stackoverflow.com/questions/5639346/what-is-the-shortest-function-for-reading-a-cookie-by-name-in-javascript
|
// 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() : '';
|
return cookieValue ? cookieValue.pop() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirectIfLoginRequird(to) {
|
function loginRequired(to) {
|
||||||
// public pages have the meta.public property set to true
|
// 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) => {
|
function unauthorizedAccess(to) {
|
||||||
if (redirectIfLoginRequird(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}`;
|
const redirectUrl = `/login?redirect=${to.path}`;
|
||||||
next(redirectUrl);
|
next(redirectUrl);
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (to.name !== 'noClass' && loginRequired(to) && await redirectStudentsWithoutClass()) {
|
||||||
|
router.push({name: 'noClass'})
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
/* eslint-disable no-new */
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="login">
|
<div class="login public-page">
|
||||||
<h1 class="login__title">Melden Sie sich jetzt an</h1>
|
<h1 class="login__title public-page__title">Melden Sie sich jetzt an</h1>
|
||||||
<form class="login__form login-form" novalidate @submit.prevent="validateBeforeSubmit">
|
<form class="login__form login-form" novalidate @submit.prevent="validateBeforeSubmit">
|
||||||
<div class="login-form__field skillboxform-input">
|
<div class="login-form__field skillboxform-input">
|
||||||
<label for="email" class="skillboxform-input__label">E-Mail</label>
|
<label for="email" class="skillboxform-input__label">E-Mail</label>
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
type="text"
|
type="text"
|
||||||
v-model="email"
|
v-model="email"
|
||||||
v-validate="'required'"
|
v-validate="'required'"
|
||||||
|
data-vv-as="E-Mail"
|
||||||
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
|
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
|
||||||
class="change-form__email skillbox-input skillboxform-input__input"
|
class="change-form__email skillbox-input skillboxform-input__input"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
|
@ -33,6 +34,7 @@
|
||||||
id="pw"
|
id="pw"
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
|
data-vv-as="Passwort"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
v-validate="'required'"
|
v-validate="'required'"
|
||||||
:class="{ 'skillboxform-input__input--error': errors.has('password') }"
|
: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>
|
<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>
|
<a class="actions__reset text-link" href="/accounts/password_reset/">Passwort vergessen?</a>
|
||||||
</div>
|
</div>
|
||||||
<!--div class="registration">
|
<div class="registration">
|
||||||
<p class="registration__text">Haben Sie noch kein Konto?</p>
|
<p class="registration__text">Haben Sie noch kein Konto?</p>
|
||||||
<a class="registration__link text-link" href="/accounts/password_reset/">Jetzt registrieren</a>
|
<router-link class="registration__link text-link" :to="{name: 'registration'}">Jetzt registrieren
|
||||||
</div-->
|
</router-link>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -92,16 +95,24 @@ export default {
|
||||||
store,
|
store,
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
login: { success }
|
login
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
if (success) {
|
if (login.success) {
|
||||||
const redirectUrl = that.$route.query.redirect ? that.$route.query.redirect : '/'
|
const redirectUrl = that.$route.query.redirect ? that.$route.query.redirect : '/'
|
||||||
that.$router.push(redirectUrl);
|
that.$router.push(redirectUrl);
|
||||||
} else {
|
} 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) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
|
@ -137,15 +148,6 @@ export default {
|
||||||
@import "@/styles/_variables.scss";
|
@import "@/styles/_variables.scss";
|
||||||
@import "@/styles/_mixins.scss";
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
.login {
|
|
||||||
&__title {
|
|
||||||
margin-top: 48px;
|
|
||||||
font-size: 2.75rem; // 44px
|
|
||||||
margin-bottom: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-link {
|
.text-link {
|
||||||
font-family: $sans-serif-font-family;
|
font-family: $sans-serif-font-family;
|
||||||
color: $color-brand;
|
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 styleGuidePage from '@/pages/styleguide'
|
||||||
import moduleRoom from '@/pages/moduleRoom'
|
import moduleRoom from '@/pages/moduleRoom'
|
||||||
import login from '@/pages/login'
|
import login from '@/pages/login'
|
||||||
|
import registration from '@/pages/registration'
|
||||||
|
import waitForClass from '@/pages/waitForClass'
|
||||||
|
|
||||||
import store from '@/store/index';
|
import store from '@/store/index';
|
||||||
|
|
||||||
|
|
@ -117,6 +119,21 @@ const routes = [
|
||||||
props: true,
|
props: true,
|
||||||
meta: {layout: 'simple'}
|
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: '/styleguide', component: styleGuidePage},
|
||||||
{path: '*', component: p404}
|
{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 "visibility";
|
||||||
@import "solutions";
|
@import "solutions";
|
||||||
@import "password_forms";
|
@import "password_forms";
|
||||||
|
@import "public-page";
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ from rooms.mutations import RoomMutations
|
||||||
from rooms.schema import RoomsQuery, ModuleRoomsQuery
|
from rooms.schema import RoomsQuery, ModuleRoomsQuery
|
||||||
from users.schema import AllUsersQuery, UsersQuery
|
from users.schema import AllUsersQuery, UsersQuery
|
||||||
from users.mutations import ProfileMutations
|
from users.mutations import ProfileMutations
|
||||||
|
from registration.mutations_public import RegistrationMutations
|
||||||
|
|
||||||
|
|
||||||
class Query(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery,
|
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,
|
class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations,
|
||||||
ProfileMutations, SurveyMutations, NoteMutations, graphene.ObjectType):
|
ProfileMutations, SurveyMutations, NoteMutations, RegistrationMutations, graphene.ObjectType):
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
debug = graphene.Field(DjangoDebug, name='__debug')
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@ from django.conf import settings
|
||||||
from graphene_django.debug import DjangoDebug
|
from graphene_django.debug import DjangoDebug
|
||||||
|
|
||||||
from users.mutations_public import UserMutations
|
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:
|
if settings.DEBUG:
|
||||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
debug = graphene.Field(DjangoDebug, name='__debug')
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ from django.core.management import BaseCommand
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from wagtail.core.models import Page
|
from wagtail.core.models import Page
|
||||||
|
|
||||||
from assignments.factories import AssignmentFactory
|
|
||||||
from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory
|
from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory
|
||||||
from core.factories import UserFactory
|
from core.factories import UserFactory
|
||||||
from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory
|
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, **self.filter_data(content_block_data, 'contents'))
|
||||||
ContentBlockFactory.create(parent=chapter, module=module, **content_block_data)
|
ContentBlockFactory.create(parent=chapter, module=module, **content_block_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# now create all and rooms
|
# now create all and rooms
|
||||||
management.call_command('dummy_rooms', verbosity=0)
|
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))
|
self.stdout.write("Creating user {} {}, {}".format(first_name, last_name, email))
|
||||||
|
|
||||||
user, created = User.objects.get_or_create(email=email, username=email)
|
user = User.objects.create_user_with_random_password(first_name, last_name, email)
|
||||||
user.first_name = first_name
|
|
||||||
user.last_name = last_name
|
|
||||||
user.set_password(User.objects.make_random_password())
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
if row['Rolle'] == 'Lehrer':
|
if row['Rolle'] == 'Lehrer':
|
||||||
self.stdout.write("Assigning teacher role")
|
self.stdout.write("Assigning teacher role")
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ INSTALLED_APPS = [
|
||||||
'statistics',
|
'statistics',
|
||||||
'surveys',
|
'surveys',
|
||||||
'notes',
|
'notes',
|
||||||
|
'registration',
|
||||||
|
|
||||||
'wagtail.contrib.forms',
|
'wagtail.contrib.forms',
|
||||||
'wagtail.contrib.redirects',
|
'wagtail.contrib.redirects',
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ $base-font-size: 15px;
|
||||||
|
|
||||||
$space: 10px;
|
$space: 10px;
|
||||||
|
|
||||||
|
$color-brand: #17A887;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
font-size: $base-font-size;
|
font-size: $base-font-size;
|
||||||
|
|
@ -115,6 +117,15 @@ input[type=text], input[type=password], input[type=email], select {
|
||||||
|
|
||||||
.reset__text {
|
.reset__text {
|
||||||
margin-bottom: 52px;
|
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 {
|
.reset__form label {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="reset">
|
<div class="reset">
|
||||||
<h2 class="reset__heading">{% trans 'Setzen Sie Ihr neues Passwort' %}</h2>
|
<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">
|
<form method="post" class="mt-1 reset__form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ 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 %}
|
{% block body %}
|
||||||
<div class="reset">
|
<div class="reset">
|
||||||
<h2 class="reset__heading">{% trans 'Sie haben es geschafft' %}</h2>
|
<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>
|
<p class="reset__text"><a href="/login">{% trans 'Jetzt anmelden' %}</a></p>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="reset">
|
<div class="reset">
|
||||||
<h2 class="reset__heading">{% trans 'Geben Sie ein persönliches Passwort ein:' %}</h2>
|
<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">
|
<form method="post" class="mt-1 reset__form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% load i18n %}{% autoescape off %}
|
{% 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:" %}
|
{% trans "Bitte öffnen Sie folgende Seite, um Ihr neues Passwort einzugeben:" %}
|
||||||
{% block reset_link %}
|
{% block reset_link %}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
from django.test.utils import override_settings
|
|
||||||
|
|
||||||
from core import settings
|
|
||||||
from core.factories import UserFactory
|
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 wagtail.core import urls as wagtail_urls
|
||||||
|
|
||||||
from core import views
|
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 = [
|
urlpatterns = [
|
||||||
# django admin
|
# django admin
|
||||||
|
|
@ -16,11 +17,20 @@ urlpatterns = [
|
||||||
url(r'^accounts/', include('django.contrib.auth.urls')),
|
url(r'^accounts/', include('django.contrib.auth.urls')),
|
||||||
url(r'^statistics/', include('statistics.urls', namespace='statistics')),
|
url(r'^statistics/', include('statistics.urls', namespace='statistics')),
|
||||||
|
|
||||||
|
# legacy - will be removed
|
||||||
# set password
|
# set password
|
||||||
path('welcome/', SetPasswordView.as_view(), name='set_password'),
|
path('welcome/', LegacySetPasswordView.as_view(), name='set_password'),
|
||||||
path('set-password/done/', SetPasswordDoneView.as_view(), name='set_password_done'),
|
path('set-password/done/', LegacySetPasswordDoneView.as_view(), name='set_password_done'),
|
||||||
path('set-password/<uidb64>/<token>/', SetPasswordConfirmView.as_view(), name='set_password_confirm'),
|
path('set-password/<uidb64>/<token>/', LegacySetPasswordConfirmView.as_view(), name='set_password_confirm'),
|
||||||
path('set-password/complete/', SetPasswordCompleteView.as_view(), name='set_password_complete'),
|
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
|
# wagtail
|
||||||
url(r'^cms/', include(wagtailadmin_urls)),
|
url(r'^cms/', include(wagtailadmin_urls)),
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,31 @@ def home(request):
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordView(PasswordResetView):
|
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'
|
email_template_name = 'registration/set_password_email.html'
|
||||||
subject_template_name = 'registration/set_password_subject.txt'
|
subject_template_name = 'registration/set_password_subject.txt'
|
||||||
success_url = reverse_lazy('set_password_done')
|
success_url = reverse_lazy('set_password_done')
|
||||||
|
|
@ -34,17 +59,17 @@ class SetPasswordView(PasswordResetView):
|
||||||
title = _('Password setzen')
|
title = _('Password setzen')
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordDoneView(PasswordResetDoneView):
|
class LegacySetPasswordDoneView(PasswordResetDoneView):
|
||||||
template_name = 'registration/set_password_done.html'
|
template_name = 'registration/set_password_done.html'
|
||||||
title = _('Password setzen versandt')
|
title = _('Password setzen versandt')
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordConfirmView(PasswordResetConfirmView):
|
class LegacySetPasswordConfirmView(PasswordResetConfirmView):
|
||||||
success_url = reverse_lazy('set_password_complete')
|
success_url = reverse_lazy('set_password_complete')
|
||||||
template_name = 'registration/set_password_confirm.html'
|
template_name = 'registration/set_password_confirm.html'
|
||||||
title = _('Gib ein Passwort ein')
|
title = _('Gib ein Passwort ein')
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordCompleteView(PasswordResetCompleteView):
|
class LegacySetPasswordCompleteView(PasswordResetCompleteView):
|
||||||
template_name = 'registration/set_password_complete.html'
|
template_name = 'registration/set_password_complete.html'
|
||||||
title = _('Passwort setzen erfolgreich')
|
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.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import UserManager as DjangoUserManager
|
||||||
|
|
||||||
|
|
||||||
class RoleManager(models.Manager):
|
class RoleManager(models.Manager):
|
||||||
|
|
@ -78,3 +79,13 @@ class UserRoleManager(models.Manager):
|
||||||
user_role = self.model(user=user, role=role)
|
user_role = self.model(user=user, role=role)
|
||||||
user_role.save()
|
user_role.save()
|
||||||
return user_role
|
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 import get_user_model
|
||||||
from django.contrib.auth.models import AbstractUser, Permission
|
from django.contrib.auth.models import AbstractUser, Permission
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
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
|
DEFAULT_SCHOOL_ID = 1
|
||||||
|
|
||||||
|
|
@ -14,6 +16,8 @@ class User(AbstractUser):
|
||||||
avatar_url = models.CharField(max_length=254, blank=True, default='')
|
avatar_url = models.CharField(max_length=254, blank=True, default='')
|
||||||
email = models.EmailField(_('email address'), unique=True)
|
email = models.EmailField(_('email address'), unique=True)
|
||||||
|
|
||||||
|
objects = UserManager()
|
||||||
|
|
||||||
def get_role_permissions(self):
|
def get_role_permissions(self):
|
||||||
perms = set()
|
perms = set()
|
||||||
for role in Role.objects.get_roles_for_user(self):
|
for role in Role.objects.get_roles_for_user(self):
|
||||||
|
|
@ -70,6 +74,25 @@ class SchoolClass(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'SchoolClass {}-{}'.format(self.id, self.name)
|
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):
|
def is_user_in_schoolclass(self, user):
|
||||||
return user.is_superuser or user.school_classes.filter(pk=self.id).count() > 0
|
return user.is_superuser or user.school_classes.filter(pk=self.id).count() > 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,16 @@
|
||||||
#
|
#
|
||||||
# Created on 2019-10-01
|
# Created on 2019-10-01
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
import re
|
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
|
|
||||||
|
from registration.models import License
|
||||||
class FieldError(graphene.ObjectType):
|
|
||||||
code = graphene.String()
|
|
||||||
|
|
||||||
|
|
||||||
class MutationError(graphene.ObjectType):
|
class LoginError(graphene.ObjectType):
|
||||||
field = graphene.String()
|
field = graphene.String()
|
||||||
errors = graphene.List(FieldError)
|
|
||||||
|
|
||||||
|
|
||||||
class Login(relay.ClientIDMutation):
|
class Login(relay.ClientIDMutation):
|
||||||
|
|
@ -29,17 +25,30 @@ class Login(relay.ClientIDMutation):
|
||||||
password_input = graphene.String()
|
password_input = graphene.String()
|
||||||
|
|
||||||
success = graphene.Boolean()
|
success = graphene.Boolean()
|
||||||
errors = graphene.List(MutationError) # todo: change for consistency
|
errors = graphene.List(LoginError) # todo: change for consistency
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
|
||||||
user = authenticate(username=kwargs.get('username_input'), password=kwargs.get('password_input'))
|
user = authenticate(username=kwargs.get('username_input'), password=kwargs.get('password_input'))
|
||||||
if user is not None:
|
if user is None:
|
||||||
login(info.context, user)
|
error = LoginError(field='invalid_credentials')
|
||||||
return cls(success=True, errors=[])
|
return cls(success=False, errors=[error])
|
||||||
else:
|
|
||||||
return cls(success=False, errors=['invalid_credentials'])
|
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:
|
class UserMutations:
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ def validate_old_new_password(value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def validate_strong_email(password):
|
def validate_strong_password(password):
|
||||||
|
|
||||||
has_number = re.search('\d', password)
|
has_number = re.search('\d', password)
|
||||||
has_upper = re.search('[A-Z]', 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)
|
new_password = CharField(allow_blank=True, min_length=MIN_PASSWORD_LENGTH)
|
||||||
|
|
||||||
def validate_new_password(self, value):
|
def validate_new_password(self, value):
|
||||||
return validate_strong_email(value)
|
return validate_strong_password(value)
|
||||||
|
|
||||||
def validate_old_password(self, value):
|
def validate_old_password(self, value):
|
||||||
return validate_old_password(value, self.context.username)
|
return validate_old_password(value, self.context.username)
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,15 @@ from graphene.test import Client
|
||||||
|
|
||||||
from api.schema_public import schema
|
from api.schema_public import schema
|
||||||
from core.factories import UserFactory
|
from core.factories import UserFactory
|
||||||
|
from registration.factories import LicenseTypeFactory, LicenseFactory
|
||||||
|
from users.models import Role
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetTests(TestCase):
|
class PasswordResetTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = UserFactory(username='aschi@iterativ.ch', email='aschi@iterativ.ch')
|
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('/')
|
request = RequestFactory().post('/')
|
||||||
|
|
||||||
|
|
@ -62,3 +66,25 @@ class PasswordResetTests(TestCase):
|
||||||
|
|
||||||
result = self.make_login_mutation(self.user.email, 'test1234')
|
result = self.make_login_mutation(self.user.email, 'test1234')
|
||||||
self.assertFalse(result.get('data').get('login').get('success'))
|
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
|
from users.factories import SchoolClassFactory
|
||||||
|
|
||||||
|
|
||||||
class PasswordUpdate(TestCase):
|
class MySchoolClasses(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = UserFactory(username='aschi')
|
self.user = UserFactory(username='aschi')
|
||||||
self.another_user = UserFactory(username='pesche')
|
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