Merge branch 'hotfix/matomo-survey-correct-title' into develop

This commit is contained in:
Ramon Wenger 2023-08-17 16:27:11 +02:00
commit 366a7dd17c
5 changed files with 144 additions and 93 deletions

View File

@ -1,8 +1,6 @@
<template> <template>
<div class="survey-page"> <div class="survey-page">
<h1 class="survey-page__title"> <h1 class="survey-page__title">{{ title }}</h1>
{{ title }}
</h1>
<div id="survey" /> <div id="survey" />
<solution <solution
@ -201,10 +199,10 @@ export default {
}, },
}); });
if (exit) { if (exit) {
matomoTrackEvent('Übung', 'Übung erfolgreich abgeschlossen', this.title); matomoTrackEvent('Übung', 'Übung erfolgreich abgeschlossen', this.surveyData.title);
this.$router.go(-1); this.$router.go(-1);
} else { } else {
matomoTrackEvent('Übung', 'Übungsschritt abgeschlossen', this.title); matomoTrackEvent('Übung', 'Übungsschritt abgeschlossen', this.surveyData.title);
} }
}; };
@ -263,7 +261,7 @@ export default {
this.surveyData = data.survey; this.surveyData = data.survey;
this.loadSurveyFromServer(data.survey); this.loadSurveyFromServer(data.survey);
const module = data.survey.module; const module = data.survey.module;
matomoTrackEvent('Übung', 'Übung angezeigt', data.survey.title); matomoTrackEvent('Übung', 'Übung angezeigt', this.surveyData.title);
this.$apollo.addSmartQuery('module', { this.$apollo.addSmartQuery('module', {
query: MODULE_QUERY, query: MODULE_QUERY,

View File

@ -12,10 +12,11 @@ from core.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
VALID_PRODUCT_STATES = ['waiting', 'paid', 'completed', 'shipped'] VALID_PRODUCT_STATES = ["waiting", "paid", "completed", "shipped"]
licenses = get_license_dict() licenses = get_license_dict()
class HepClientException(Exception): class HepClientException(Exception):
pass pass
@ -29,16 +30,16 @@ class HepClientNoTokenException(Exception):
class HepClient: class HepClient:
def _call(self, url, token_dict, method="get", data=None):
def _call(self, url, token_dict, method='get', data=None):
headers = {"accept": "application/json"} headers = {"accept": "application/json"}
if method == 'post': if method == "post":
response = oauth.hep.post(url, json=data, token=token_dict, headers=headers) response = oauth.hep.post(url, json=data, token=token_dict, headers=headers)
elif method == 'get': elif method == "get":
response = oauth.hep.get(url, params=data, token=token_dict, headers=headers) response = oauth.hep.get(
elif method == 'put': url, params=data, token=token_dict, headers=headers
)
elif method == "put":
response = oauth.hep.put(url, data=data, token=token_dict, headers=headers) response = oauth.hep.put(url, data=data, token=token_dict, headers=headers)
return self._handle_response(response) return self._handle_response(response)
@ -47,7 +48,9 @@ class HepClient:
if response.status_code == 401: if response.status_code == 401:
raise HepClientUnauthorizedException(response.status_code, response.json()) raise HepClientUnauthorizedException(response.status_code, response.json())
elif response.status_code != 200: elif response.status_code != 200:
logger.warning(f'Hepclient error: Received {response.status_code} {response.json()}') logger.warning(
f"Hepclient error: Received {response.status_code} {response.json()}"
)
raise HepClientException(response.status_code, response.json()) raise HepClientException(response.status_code, response.json())
return response return response
@ -57,13 +60,15 @@ class HepClient:
raise HepClientNoTokenException raise HepClientNoTokenException
if not token_dict: if not token_dict:
token_dict = fetch_token('', request) token_dict = fetch_token("", request)
if not token_dict: if not token_dict:
raise HepClientNoTokenException raise HepClientNoTokenException
if OAuth2Token.is_dict_expired(token_dict): if OAuth2Token.is_dict_expired(token_dict):
refresh_data = self._refresh_token(token_dict) refresh_data = self._refresh_token(token_dict)
token, refresh_success = OAuth2Token.update_dict_with_refresh_data(refresh_data, token_dict['access_token']) token, refresh_success = OAuth2Token.update_dict_with_refresh_data(
refresh_data, token_dict["access_token"]
)
if not refresh_success: if not refresh_success:
raise HepClientUnauthorizedException raise HepClientUnauthorizedException
@ -72,38 +77,40 @@ class HepClient:
return token_dict return token_dict
def _refresh_token(self, token_dict): def _refresh_token(self, token_dict):
data = { data = {
'grant_type': 'refresh_token', "grant_type": "refresh_token",
'refresh_token': token_dict['refresh_token'], "refresh_token": token_dict["refresh_token"],
'client_id': settings.AUTHLIB_OAUTH_CLIENTS['hep']['client_id'], "client_id": settings.AUTHLIB_OAUTH_CLIENTS["hep"]["client_id"],
'client_secret': settings.AUTHLIB_OAUTH_CLIENTS['hep']['client_secret'], "client_secret": settings.AUTHLIB_OAUTH_CLIENTS["hep"]["client_secret"],
'scope': '' "scope": "",
} }
response = requests.post(f'{settings.AUTHLIB_OAUTH_CLIENTS["hep"]["api_base_url"]}/oauth/token', json=data) response = requests.post(
f'{settings.AUTHLIB_OAUTH_CLIENTS["hep"]["api_base_url"]}/oauth/token',
json=data,
)
return self._handle_response(response).json() return self._handle_response(response).json()
def is_email_verified(self, user_data): def is_email_verified(self, user_data):
return user_data['email_verified_at'] is not None return user_data["email_verified_at"] is not None
def user_details(self, request=None, token_dict=None): def user_details(self, request=None, token_dict=None):
token_dict = self._get_valid_token(request, token_dict) token_dict = self._get_valid_token(request, token_dict)
response = self._call('api/auth/user', token_dict) response = self._call("api/auth/user", token_dict)
return response.json()['data'] return response.json()["data"]
def logout(self, request=None, token_dict=None): def logout(self, request=None, token_dict=None):
token_dict = self._get_valid_token(request, token_dict) token_dict = self._get_valid_token(request, token_dict)
self._call('api/auth/logout', token_dict, method='post') self._call("api/auth/logout", token_dict, method="post")
return True return True
def fetch_eorders(self, request=None, token_dict=None): def fetch_eorders(self, request=None, token_dict=None):
token_dict = self._get_valid_token(request, token_dict) token_dict = self._get_valid_token(request, token_dict)
data = { data = {
'filters[product_type]': 'eLehrmittel', "filters[product_type]": "eLehrmittel",
} }
response = self._call('api/partners/users/orders/search', token_dict, data=data) response = self._call("api/partners/users/orders/search", token_dict, data=data)
return response.json()['data'] return response.json()["data"]
def active_myskillbox_product_for_customer(self, request=None, token_dict=None): def active_myskillbox_product_for_customer(self, request=None, token_dict=None):
eorders = self.fetch_eorders(request=request, token_dict=token_dict) eorders = self.fetch_eorders(request=request, token_dict=token_dict)
@ -118,9 +125,14 @@ class HepClient:
def redeem_coupon(self, coupon_code, customer_id, request=None, token_dict=None): def redeem_coupon(self, coupon_code, customer_id, request=None, token_dict=None):
token_dict = self._get_valid_token(request, token_dict) token_dict = self._get_valid_token(request, token_dict)
try: try:
response = self._call(f'api/partners/users/{customer_id}/coupons/redeem', token_dict, method='post', response = self._call(
data={'code': coupon_code}) f"api/partners/users/{customer_id}/coupons/redeem",
token_dict,
method="post",
data={"code": coupon_code},
)
except HepClientException: except HepClientException:
logger.warning("could not redeem")
return None return None
response_data = response.json() response_data = response.json()
@ -130,14 +142,15 @@ class HepClient:
products = [] products = []
for eorder in eorders: for eorder in eorders:
if "items" not in eorder:
if 'items' not in eorder:
continue continue
status = eorder.get('status', '') status = eorder.get("status", "")
for entry in eorder['items']: for entry in eorder["items"]:
product = self.entry_to_product(entry, self._get_item_activation(eorder), status) product = self.entry_to_product(
entry, self._get_item_activation(eorder), status
)
if product: if product:
products.append(product) products.append(product)
@ -145,35 +158,35 @@ class HepClient:
return products return products
def entry_to_product(self, entry, activation_date, status): def entry_to_product(self, entry, activation_date, status):
isbn = entry['isbn'] isbn = entry["isbn"]
if is_myskillbox_product(isbn) and activation_date: if is_myskillbox_product(isbn) and activation_date:
return { return {
'raw': entry, "raw": entry,
'activated': activation_date, "activated": activation_date,
'status': status, "status": status,
'order_id': entry['id'], "order_id": entry["id"],
'license': licenses[isbn], "license": licenses[isbn],
'isbn': entry['isbn'] "isbn": entry["isbn"],
} }
return None return None
def _get_item_activation(self, eorder): def _get_item_activation(self, eorder):
if 'created_at' in eorder: if "created_at" in eorder:
return parse_datetime(eorder['created_at']) return parse_datetime(eorder["created_at"])
else: else:
return None return None
def _get_active_product(self, products): def _get_active_product(self, products):
def filter_valid_products(product): def filter_valid_products(product):
if product["status"] not in VALID_PRODUCT_STATES:
if product['status'] not in VALID_PRODUCT_STATES:
return False return False
expiry_delta = product['activated'] + timedelta(product['license']['duration']) expiry_delta = product["activated"] + timedelta(
product["license"]["duration"]
)
return License.is_product_active(expiry_delta, product['isbn']) return License.is_product_active(expiry_delta, product["isbn"])
active_products = list(filter(filter_valid_products, products)) active_products = list(filter(filter_valid_products, products))
@ -185,10 +198,9 @@ class HepClient:
return self._select_from_teacher_products(active_products) return self._select_from_teacher_products(active_products)
def _select_from_teacher_products(self, active_products): def _select_from_teacher_products(self, active_products):
# select first teacher product, as they are all valid it does not matter which one # select first teacher product, as they are all valid it does not matter which one
for product in active_products: for product in active_products:
if product['license']['edition'] == TEACHER_KEY: if product["license"]["edition"] == TEACHER_KEY:
return product return product
# select a student product, as they are all valid it does not matter which one # select a student product, as they are all valid it does not matter which one

View File

@ -2,6 +2,7 @@ import graphene
from django.contrib.auth import logout from django.contrib.auth import logout
from django.utils.timezone import now from django.utils.timezone import now
from graphene import relay from graphene import relay
from core.logger import get_logger
from oauth.hep_client import HepClient, HepClientException from oauth.hep_client import HepClient, HepClientException
from oauth.models import OAuth2Token from oauth.models import OAuth2Token
@ -9,6 +10,8 @@ from oauth.types import InvalidCouponFailure, RedeemCouponResult, RedeemCouponSu
from oauth.user_signup_login_handler import create_role_for_user from oauth.user_signup_login_handler import create_role_for_user
from users.models import License from users.models import License
logger = get_logger(__name__)
class Coupon(relay.ClientIDMutation): class Coupon(relay.ClientIDMutation):
class Input: class Input:
@ -20,6 +23,7 @@ class Coupon(relay.ClientIDMutation):
def mutate_and_get_payload(cls, root, info, **kwargs): def mutate_and_get_payload(cls, root, info, **kwargs):
coupon_code = kwargs.get("coupon_code").strip() coupon_code = kwargs.get("coupon_code").strip()
hep_client = HepClient() hep_client = HepClient()
logger.info("redeeming coupon")
try: try:
hep_id = info.context.user.hep_id hep_id = info.context.user.hep_id
@ -31,7 +35,8 @@ class Coupon(relay.ClientIDMutation):
response = hep_client.redeem_coupon( response = hep_client.redeem_coupon(
coupon_code, hep_id, request=info.context coupon_code, hep_id, request=info.context
) )
except HepClientException: except HepClientException as e:
logger.warning(e)
raise Exception("unknown_error") raise Exception("unknown_error")
if not response: if not response:
@ -40,8 +45,10 @@ class Coupon(relay.ClientIDMutation):
product = hep_client.entry_to_product(response["data"], now(), "coupon") product = hep_client.entry_to_product(response["data"], now(), "coupon")
if not product: if not product:
logger.info("no myskillbox-product found")
raise Exception("non_myskillbox_product") raise Exception("non_myskillbox_product")
logger.info("creating license")
license = License.objects.create_license_for_role( license = License.objects.create_license_for_role(
info.context.user, info.context.user,
product["activated"], product["activated"],
@ -51,6 +58,7 @@ class Coupon(relay.ClientIDMutation):
product["isbn"], product["isbn"],
) )
logger.info("creating role for user")
create_role_for_user(info.context.user, license.for_role.key) create_role_for_user(info.context.user, license.for_role.key)
return cls(result=RedeemCouponSuccess) return cls(result=RedeemCouponSuccess)

View File

@ -1,11 +1,14 @@
from core.logger import get_logger
from oauth.hep_client import HepClient, HepClientException from oauth.hep_client import HepClient, HepClientException
from users.models import License from users.models import License
from users.models import User, UserRole, Role, SchoolClass from users.models import User, UserRole, Role, SchoolClass
EMAIL_NOT_VERIFIED = 'email_not_verified' EMAIL_NOT_VERIFIED = "email_not_verified"
UNKNOWN_ERROR = 'unknown_error' UNKNOWN_ERROR = "unknown_error"
NO_VALID_LICENSE = 'no_valid_license' NO_VALID_LICENSE = "no_valid_license"
logger = get_logger(__name__)
def handle_user_and_verify_products(user_data, token): def handle_user_and_verify_products(user_data, token):
@ -15,6 +18,7 @@ def handle_user_and_verify_products(user_data, token):
try: try:
if not hep_client.is_email_verified(user_data): if not hep_client.is_email_verified(user_data):
logger.info("email not verified")
return user, EMAIL_NOT_VERIFIED return user, EMAIL_NOT_VERIFIED
except HepClientException: except HepClientException:
return user, UNKNOWN_ERROR return user, UNKNOWN_ERROR
@ -22,14 +26,18 @@ def handle_user_and_verify_products(user_data, token):
license = License.objects.get_active_license_for_user(user) license = License.objects.get_active_license_for_user(user)
if not license: if not license:
logger.info("license not found, trying to create")
license, error_msg = check_and_create_licenses(hep_client, user, token) license, error_msg = check_and_create_licenses(hep_client, user, token)
if error_msg: if error_msg:
logger.warning(error_msg)
return user, error_msg return user, error_msg
logger.info("creating role")
create_role_for_user(user, license.for_role.key) create_role_for_user(user, license.for_role.key)
if license and not license.is_valid(): if license and not license.is_valid():
logger.info("license is not valid")
return user, NO_VALID_LICENSE return user, NO_VALID_LICENSE
return user, None return user, None
@ -37,15 +45,23 @@ def handle_user_and_verify_products(user_data, token):
def check_and_create_licenses(hep_client, user, token): def check_and_create_licenses(hep_client, user, token):
try: try:
logger.info("fetching products")
product = hep_client.active_myskillbox_product_for_customer(token_dict=token) product = hep_client.active_myskillbox_product_for_customer(token_dict=token)
except HepClientException: except HepClientException:
return None, UNKNOWN_ERROR return None, UNKNOWN_ERROR
if product: if product:
license = License.objects.create_license_for_role(user, product['activated'], product['raw'], logger.info("product found, creating license")
product['license']['edition'], license = License.objects.create_license_for_role(
product['order_id'], product['isbn']) user,
product["activated"],
product["raw"],
product["license"]["edition"],
product["order_id"],
product["isbn"],
)
else: else:
logger.info("no product found")
return None, NO_VALID_LICENSE return None, NO_VALID_LICENSE
return license, None return license, None
@ -59,4 +75,3 @@ def create_role_for_user(user, role_key):
if SchoolClass.objects.filter(users__in=[user]).count() == 0: if SchoolClass.objects.filter(users__in=[user]).count() == 0:
SchoolClass.create_default_group_for_teacher(user) SchoolClass.create_default_group_for_teacher(user)

View File

@ -8,21 +8,25 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.db import models from django.db import models
from django.contrib.auth.models import UserManager as DjangoUserManager from django.contrib.auth.models import UserManager as DjangoUserManager
from core.logger import get_logger
from users.licenses import get_license_dict from users.licenses import get_license_dict
license_dict = get_license_dict() license_dict = get_license_dict()
logger = get_logger(__name__)
class RoleManager(models.Manager): class RoleManager(models.Manager):
use_in_migrations = True use_in_migrations = True
TEACHER_KEY = 'teacher' TEACHER_KEY = "teacher"
STUDENT_KEY = 'student' STUDENT_KEY = "student"
PARENT_KEY = 'parent' PARENT_KEY = "parent"
DEFAULT_ROLES = { DEFAULT_ROLES = {
TEACHER_KEY: _(u'Lehrperson'), TEACHER_KEY: _("Lehrperson"),
STUDENT_KEY: _(u'Schüler'), STUDENT_KEY: _("Schüler"),
# PARENT_KEY: _(u'Aufsichtsperson'), # PARENT_KEY: _(u'Aufsichtsperson'),
# SCHOOL_ADMIN_KEY: _(u'Schuladministrator') # SCHOOL_ADMIN_KEY: _(u'Schuladministrator')
} }
@ -44,7 +48,7 @@ class RoleManager(models.Manager):
for key, value in self.DEFAULT_ROLES.items(): for key, value in self.DEFAULT_ROLES.items():
role, created = self.get_or_create(name=value, key=key) role, created = self.get_or_create(name=value, key=key)
can_manage_school_class_content, = self._create_default_permissions() (can_manage_school_class_content,) = self._create_default_permissions()
if key == "teacher": if key == "teacher":
role.role_permission.add(can_manage_school_class_content.id) role.role_permission.add(can_manage_school_class_content.id)
@ -67,15 +71,17 @@ class RoleManager(models.Manager):
# edit_own_comments = Permission.objects.get(content_type=content_type, codename="can_edit_own_comments") # edit_own_comments = Permission.objects.get(content_type=content_type, codename="can_edit_own_comments")
# delete_comments = Permission.objects.get(content_type=content_type, codename="can_delete_comments") # delete_comments = Permission.objects.get(content_type=content_type, codename="can_delete_comments")
# admin_school = Permission.objects.get(content_type=content_type, codename="can_admin_school") # admin_school = Permission.objects.get(content_type=content_type, codename="can_admin_school")
can_manage_school_class_content = Permission.objects.get(content_type=content_type, can_manage_school_class_content = Permission.objects.get(
codename='can_manage_school_class_content') content_type=content_type, codename="can_manage_school_class_content"
)
return can_manage_school_class_content, return (can_manage_school_class_content,)
class UserRoleManager(models.Manager): class UserRoleManager(models.Manager):
def get_or_create_role_for_user(self, user, role_key): def get_or_create_role_for_user(self, user, role_key):
from users.models import Role from users.models import Role
try: try:
role = Role.objects.get(key=role_key) role = Role.objects.get(key=role_key)
except Role.DoesNotExist: except Role.DoesNotExist:
@ -94,7 +100,6 @@ class UserRoleManager(models.Manager):
class UserManager(DjangoUserManager): class UserManager(DjangoUserManager):
def _create_user_with_random_password_no_save(self, first_name, last_name, email): def _create_user_with_random_password_no_save(self, first_name, last_name, email):
user, created = self.model.objects.get_or_create(email=email, username=email) user, created = self.model.objects.get_or_create(email=email, username=email)
user.first_name = first_name user.first_name = first_name
@ -105,20 +110,22 @@ class UserManager(DjangoUserManager):
return user return user
def create_user_with_random_password(self, first_name, last_name, email): def create_user_with_random_password(self, first_name, last_name, email):
user = self._create_user_with_random_password_no_save(first_name, last_name, email) user = self._create_user_with_random_password_no_save(
first_name, last_name, email
)
user.save() user.save()
return user return user
def create_user_from_hep(self, user_data): def create_user_from_hep(self, user_data):
try: try:
user = self.model.objects.get(email=user_data['email']) user = self.model.objects.get(email=user_data["email"])
user.set_unusable_password() user.set_unusable_password()
except self.model.DoesNotExist: except self.model.DoesNotExist:
user = self._create_user_with_random_password_no_save(user_data['first_name'], user = self._create_user_with_random_password_no_save(
user_data['last_name'], user_data["first_name"], user_data["last_name"], user_data["email"]
user_data['email']) )
user.hep_id = user_data['id'] user.hep_id = user_data["id"]
user.save() user.save()
# todo: how to handle # todo: how to handle
@ -128,36 +135,48 @@ class UserManager(DjangoUserManager):
return user return user
def get_or_create_hep_user(self, user_data): def get_or_create_hep_user(self, user_data):
try: try:
user = self.get(hep_id=user_data['id']) user = self.get(hep_id=user_data["id"])
logger.info(f"Got user {user.id}")
except self.model.DoesNotExist: except self.model.DoesNotExist:
user = self.create_user_from_hep(user_data) user = self.create_user_from_hep(user_data)
logger.info(f"Created user {user.id}")
return user return user
class LicenseManager(models.Manager): class LicenseManager(models.Manager):
def create_license_for_role(
self, licensee, activation_date, raw, role, order_id, isbn
):
Role = apps.get_model("users", "Role")
expiry_date = activation_date + timedelta(license_dict[isbn]["duration"])
def create_license_for_role(self, licensee, activation_date, raw, role, order_id, isbn): if role == "teacher":
Role = apps.get_model('users', 'Role')
expiry_date = activation_date + timedelta(license_dict[isbn]['duration'])
if role == 'teacher':
user_role = Role.objects.get_default_teacher_role() user_role = Role.objects.get_default_teacher_role()
else: else:
user_role = Role.objects.get_default_student_role() user_role = Role.objects.get_default_student_role()
new_license = self._create_license_for_role(licensee, expiry_date, raw, user_role, order_id, isbn, new_license = self._create_license_for_role(
activation_date) licensee, expiry_date, raw, user_role, order_id, isbn, activation_date
)
new_license.licensee.license_expiry_date = new_license.expire_date new_license.licensee.license_expiry_date = new_license.expire_date
new_license.licensee.save() new_license.licensee.save()
return new_license return new_license
def _create_license_for_role(self, licensee, expiry_date, raw, role, order_id, isbn, activation_date): def _create_license_for_role(
return self.create(licensee=licensee, expire_date=expiry_date, new_api_raw=raw, for_role=role, order_id=order_id, self, licensee, expiry_date, raw, role, order_id, isbn, activation_date
isbn=isbn, hep_created_at=activation_date) ):
return self.create(
licensee=licensee,
expire_date=expiry_date,
new_api_raw=raw,
for_role=role,
order_id=order_id,
isbn=isbn,
hep_created_at=activation_date,
)
def get_active_license_for_user(self, user): def get_active_license_for_user(self, user):
licenses = self.filter(licensee=user, expire_date__gte=timezone.now()).order_by( licenses = self.filter(licensee=user, expire_date__gte=timezone.now()).order_by(
@ -174,4 +193,3 @@ class LicenseManager(models.Manager):
user.save() user.save()
return license return license