Refactor completion api
This commit is contained in:
parent
12322638dc
commit
3686924cfe
|
|
@ -11,12 +11,12 @@ const props = defineProps<{
|
||||||
const circleStore = useCircleStore();
|
const circleStore = useCircleStore();
|
||||||
|
|
||||||
function toggleCompleted(learningContent: LearningContent) {
|
function toggleCompleted(learningContent: LearningContent) {
|
||||||
circleStore.toggleCompleted(learningContent, !learningContent.completed);
|
circleStore.markCompletion(learningContent, !learningContent.completed);
|
||||||
}
|
}
|
||||||
|
|
||||||
const someFinished = computed(() => {
|
const someFinished = computed(() => {
|
||||||
if (props.learningSequence) {
|
if (props.learningSequence) {
|
||||||
return circleStore.flatLearningContents.filter((lc) => {
|
return circleStore.flatChildren.filter((lc) => {
|
||||||
return lc.completed && lc.parentLearningSequence?.translation_key === props.learningSequence.translation_key;
|
return lc.completed && lc.parentLearningSequence?.translation_key === props.learningSequence.translation_key;
|
||||||
}).length > 0;
|
}).length > 0;
|
||||||
}
|
}
|
||||||
|
|
@ -68,11 +68,30 @@ const someFinished = computed(() => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="learningUnit.id"
|
v-if="learningUnit.id"
|
||||||
class="flex items-center gap-4 pb-3 hover:cursor-pointer"
|
class="hover:cursor-pointer"
|
||||||
@click="circleStore.openSelfEvaluation(learningUnit)"
|
@click="circleStore.openSelfEvaluation(learningUnit)"
|
||||||
>
|
>
|
||||||
<it-icon-smiley-neutral/>
|
<div
|
||||||
<span>Selbsteinschätzung</span>
|
v-if="circleStore.calcSelfEvaluationStatus(learningUnit)"
|
||||||
|
class="flex items-center gap-4 pb-3"
|
||||||
|
>
|
||||||
|
<it-icon-smiley-happy/>
|
||||||
|
<span>Selbsteinschätzung: Ich kann das.</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="circleStore.calcSelfEvaluationStatus(learningUnit) === false"
|
||||||
|
class="flex items-center gap-4 pb-3"
|
||||||
|
>
|
||||||
|
<it-icon-smiley-thinking/>
|
||||||
|
<span>Selbsteinschätzung: Muss ich nochmals anschauen</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="flex items-center gap-4 pb-3"
|
||||||
|
>
|
||||||
|
<it-icon-smiley-neutral/>
|
||||||
|
<span>Selbsteinschätzung</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="-mx-4 text-gray-500">
|
<hr class="-mx-4 text-gray-500">
|
||||||
|
|
|
||||||
|
|
@ -57,13 +57,29 @@ const currentQuestion = computed(() => questions.value[questionIndex]);
|
||||||
<h2 class="heading-2">{{ currentQuestion.title }}</h2>
|
<h2 class="heading-2">{{ currentQuestion.title }}</h2>
|
||||||
|
|
||||||
<div class="mt-12 flex justify-between gap-6">
|
<div class="mt-12 flex justify-between gap-6">
|
||||||
<button class="flex-1 inline-flex items-center text-left p-4 border border-gray-500">
|
<button
|
||||||
|
@click="circleStore.markCompletion(currentQuestion, true)"
|
||||||
|
class="flex-1 inline-flex items-center text-left p-4 border"
|
||||||
|
:class="{
|
||||||
|
'border-green-500': currentQuestion.completed,
|
||||||
|
'border-2': currentQuestion.completed,
|
||||||
|
'border-gray-500': !currentQuestion.completed,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy>
|
<it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy>
|
||||||
<span class="font-bold text-xl">
|
<span class="font-bold text-xl">
|
||||||
Ja, ich kann das.
|
Ja, ich kann das.
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="flex-1 inline-flex items-center text-left p-4 border border-gray-500">
|
<button
|
||||||
|
@click="circleStore.markCompletion(currentQuestion, false)"
|
||||||
|
class="flex-1 inline-flex items-center text-left p-4 border"
|
||||||
|
:class="{
|
||||||
|
'border-orange-500': currentQuestion.completed === false,
|
||||||
|
'border-2': currentQuestion.completed === false,
|
||||||
|
'border-gray-500': currentQuestion.completed === true || currentQuestion.completed === undefined,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking>
|
<it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking>
|
||||||
<span class="font-bold text-xl">
|
<span class="font-bold text-xl">
|
||||||
Das muss ich nochmals anschauen.
|
Das muss ich nochmals anschauen.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ function createEmptyLearningUnit(parentLearningSequence: LearningSequence): Lear
|
||||||
slug: '',
|
slug: '',
|
||||||
translation_key: '',
|
translation_key: '',
|
||||||
type: 'learnpath.LearningUnit',
|
type: 'learnpath.LearningUnit',
|
||||||
questions: [],
|
|
||||||
learningContents: [],
|
learningContents: [],
|
||||||
minutes: 0,
|
minutes: 0,
|
||||||
parentLearningSequence: parentLearningSequence,
|
parentLearningSequence: parentLearningSequence,
|
||||||
|
|
@ -49,6 +48,7 @@ export function parseLearningSequences (children: CircleChild[]): LearningSequen
|
||||||
parentLearningSequence: learningSequence,
|
parentLearningSequence: learningSequence,
|
||||||
children: child.children.map((c) => {
|
children: child.children.map((c) => {
|
||||||
c.parentLearningUnit = learningUnit;
|
c.parentLearningUnit = learningUnit;
|
||||||
|
c.parentLearningSequence = learningSequence;
|
||||||
return c;
|
return c;
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ import * as log from 'loglevel';
|
||||||
|
|
||||||
import {defineStore} from 'pinia'
|
import {defineStore} from 'pinia'
|
||||||
|
|
||||||
import type {Circle, LearningContent, LearningUnit} from '@/types'
|
import type {Circle, CircleChild, CircleCompletion, LearningContent, LearningUnit, LearningUnitQuestion} from '@/types'
|
||||||
import {itGet, itPost} from '@/fetchHelpers';
|
import {itGet, itPost} from '@/fetchHelpers';
|
||||||
import {parseLearningSequences} from '@/services/circle';
|
import {parseLearningSequences} from '@/services/circle';
|
||||||
|
|
||||||
export type CircleStoreState = {
|
export type CircleStoreState = {
|
||||||
circleData: Circle;
|
circleData: Circle;
|
||||||
completionData: any;
|
completionData: CircleCompletion[];
|
||||||
currentLearningContent: LearningContent | undefined;
|
currentLearningContent: LearningContent | undefined;
|
||||||
currentSelfEvaluation: LearningUnit | undefined;
|
currentSelfEvaluation: LearningUnit | undefined;
|
||||||
page: 'INDEX' | 'OVERVIEW' | 'LEARNING_CONTENT' | 'SELF_EVALUATION';
|
page: 'INDEX' | 'OVERVIEW' | 'LEARNING_CONTENT' | 'SELF_EVALUATION';
|
||||||
|
|
@ -26,10 +26,13 @@ export const useCircleStore = defineStore({
|
||||||
} as CircleStoreState;
|
} as CircleStoreState;
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
flatLearningContents: (state) => {
|
flatChildren: (state) => {
|
||||||
const result:LearningContent[] = [];
|
const result:CircleChild[] = [];
|
||||||
state.circleData.learningSequences.forEach((learningSequence) => {
|
state.circleData.learningSequences.forEach((learningSequence) => {
|
||||||
learningSequence.learningUnits.forEach((learningUnit) => {
|
learningSequence.learningUnits.forEach((learningUnit) => {
|
||||||
|
learningUnit.children.forEach((learningUnitQuestion) => {
|
||||||
|
result.push(learningUnitQuestion);
|
||||||
|
})
|
||||||
learningUnit.learningContents.forEach((learningContent) => {
|
learningUnit.learningContents.forEach((learningContent) => {
|
||||||
result.push(learningContent);
|
result.push(learningContent);
|
||||||
});
|
});
|
||||||
|
|
@ -42,7 +45,6 @@ export const useCircleStore = defineStore({
|
||||||
async loadCircle(slug: string) {
|
async loadCircle(slug: string) {
|
||||||
try {
|
try {
|
||||||
this.circleData = await itGet(`/learnpath/api/circle/${slug}/`);
|
this.circleData = await itGet(`/learnpath/api/circle/${slug}/`);
|
||||||
console.log(this.circleData);
|
|
||||||
this.circleData.learningSequences = parseLearningSequences(this.circleData.children);
|
this.circleData.learningSequences = parseLearningSequences(this.circleData.children);
|
||||||
this.completionData = await itGet(`/api/completion/circle/${this.circleData.translation_key}/`);
|
this.completionData = await itGet(`/api/completion/circle/${this.circleData.translation_key}/`);
|
||||||
this.parseCompletionData();
|
this.parseCompletionData();
|
||||||
|
|
@ -51,12 +53,12 @@ export const useCircleStore = defineStore({
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async toggleCompleted(learningContent: LearningContent, flag = true) {
|
async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) {
|
||||||
try {
|
try {
|
||||||
learningContent.completed = flag;
|
page.completed = flag;
|
||||||
this.completionData = await itPost('/api/completion/complete_learning_content/', {
|
this.completionData = await itPost('/api/completion/circle/mark/', {
|
||||||
learning_content_key: learningContent.translation_key,
|
page_key: page.translation_key,
|
||||||
completed: learningContent.completed,
|
completed: page.completed,
|
||||||
});
|
});
|
||||||
this.parseCompletionData();
|
this.parseCompletionData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -65,10 +67,15 @@ export const useCircleStore = defineStore({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parseCompletionData() {
|
parseCompletionData() {
|
||||||
this.flatLearningContents.forEach((learningContent) => {
|
this.flatChildren.forEach((page) => {
|
||||||
learningContent.completed = this.completionData.completed_learning_contents.findIndex((e) => {
|
const pageIndex = this.completionData.findIndex((e) => {
|
||||||
return e.learning_content_key === learningContent.translation_key && e.completed;
|
return e.page_key === page.translation_key;
|
||||||
}) >= 0;
|
});
|
||||||
|
if (pageIndex >= 0) {
|
||||||
|
page.completed = this.completionData[pageIndex].completed;
|
||||||
|
} else {
|
||||||
|
page.completed = undefined;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
openLearningContent(learningContent: LearningContent) {
|
openLearningContent(learningContent: LearningContent) {
|
||||||
|
|
@ -87,9 +94,21 @@ export const useCircleStore = defineStore({
|
||||||
this.page = 'INDEX';
|
this.page = 'INDEX';
|
||||||
this.currentSelfEvaluation = undefined;
|
this.currentSelfEvaluation = undefined;
|
||||||
},
|
},
|
||||||
|
calcSelfEvaluationStatus(learningUnit: LearningUnit) {
|
||||||
|
if (learningUnit.children.length > 0) {
|
||||||
|
if (learningUnit.children.every((q) => q.completed)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (learningUnit.children.some((q) => q.completed !== undefined)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
,
|
||||||
continueToNextLearningContent() {
|
continueToNextLearningContent() {
|
||||||
if (this.currentLearningContent) {
|
if (this.currentLearningContent) {
|
||||||
this.toggleCompleted(this.currentLearningContent, true);
|
this.markCompletion(this.currentLearningContent, true);
|
||||||
if (this.currentLearningContent.nextLearningContent) {
|
if (this.currentLearningContent.nextLearningContent) {
|
||||||
this.openLearningContent(this.currentLearningContent.nextLearningContent);
|
this.openLearningContent(this.currentLearningContent.nextLearningContent);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,13 @@ export interface LearningWagtailPage {
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
translation_key: string;
|
translation_key: string;
|
||||||
|
completed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LearningContent extends LearningWagtailPage {
|
export interface LearningContent extends LearningWagtailPage {
|
||||||
type: 'learnpath.LearningContent';
|
type: 'learnpath.LearningContent';
|
||||||
minutes: number;
|
minutes: number;
|
||||||
contents: (LearningContentBlock | VideoBlock | PodcastBlock | DocumentBlock)[];
|
contents: (LearningContentBlock | VideoBlock | PodcastBlock | DocumentBlock)[];
|
||||||
completed?: boolean;
|
|
||||||
parentLearningSequence?: LearningSequence;
|
parentLearningSequence?: LearningSequence;
|
||||||
parentLearningUnit?: LearningUnit;
|
parentLearningUnit?: LearningUnit;
|
||||||
nextLearningContent?: LearningContent;
|
nextLearningContent?: LearningContent;
|
||||||
|
|
@ -65,6 +65,7 @@ export interface LearningContent extends LearningWagtailPage {
|
||||||
|
|
||||||
export interface LearningUnitQuestion extends LearningWagtailPage {
|
export interface LearningUnitQuestion extends LearningWagtailPage {
|
||||||
type: 'learnpath.LearningUnitQuestion';
|
type: 'learnpath.LearningUnitQuestion';
|
||||||
|
parentLearningSequence?: LearningSequence;
|
||||||
parentLearningUnit?: LearningUnit;
|
parentLearningUnit?: LearningUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +84,7 @@ export interface LearningSequence extends LearningWagtailPage {
|
||||||
minutes: number;
|
minutes: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CircleChild = LearningContent | LearningUnit | LearningSequence;
|
export type CircleChild = LearningContent | LearningUnit | LearningSequence | LearningUnitQuestion;
|
||||||
|
|
||||||
export interface WagtailCircle extends LearningWagtailPage {
|
export interface WagtailCircle extends LearningWagtailPage {
|
||||||
type: 'learnpath.Circle';
|
type: 'learnpath.Circle';
|
||||||
|
|
@ -91,6 +92,18 @@ export interface WagtailCircle extends LearningWagtailPage {
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CircleCompletion {
|
||||||
|
id: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
user: number;
|
||||||
|
page_key: string;
|
||||||
|
page_type: string;
|
||||||
|
circle_key: string;
|
||||||
|
completed: boolean;
|
||||||
|
json_data: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Circle extends LearningWagtailPage {
|
export interface Circle extends LearningWagtailPage {
|
||||||
type: 'learnpath.Circle';
|
type: 'learnpath.Circle';
|
||||||
children: CircleChild[];
|
children: CircleChild[];
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 3.2.13 on 2022-06-08 13:52
|
# Generated by Django 3.2.13 on 2022-06-22 16:53
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
@ -15,52 +15,21 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UserCircleCompletion',
|
name='CircleCompletion',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('page_key', models.UUIDField()),
|
||||||
|
('page_type', models.CharField(blank=True, default='', max_length=255)),
|
||||||
('circle_key', models.UUIDField()),
|
('circle_key', models.UUIDField()),
|
||||||
('json_data', models.JSONField(blank=True, default=dict)),
|
('completed', models.BooleanField(default=False)),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='LearningUnitQuestionCompletion',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('question_key', models.UUIDField()),
|
|
||||||
('circle_key', models.UUIDField()),
|
|
||||||
('completed', models.BooleanField(default=True)),
|
|
||||||
('json_data', models.JSONField(blank=True, default=dict)),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='LearningContentCompletion',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('learning_content_key', models.UUIDField()),
|
|
||||||
('circle_key', models.UUIDField()),
|
|
||||||
('completed', models.BooleanField(default=True)),
|
|
||||||
('json_data', models.JSONField(blank=True, default=dict)),
|
('json_data', models.JSONField(blank=True, default=dict)),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AddConstraint(
|
migrations.AddConstraint(
|
||||||
model_name='usercirclecompletion',
|
model_name='circlecompletion',
|
||||||
constraint=models.UniqueConstraint(fields=('user', 'circle_key'), name='unique_user_circle_completion'),
|
constraint=models.UniqueConstraint(fields=('user', 'page_key'), name='unique_user_page_key'),
|
||||||
),
|
|
||||||
migrations.AddConstraint(
|
|
||||||
model_name='learningunitquestioncompletion',
|
|
||||||
constraint=models.UniqueConstraint(fields=('user', 'question_key'), name='unique_user_question_key'),
|
|
||||||
),
|
|
||||||
migrations.AddConstraint(
|
|
||||||
model_name='learningcontentcompletion',
|
|
||||||
constraint=models.UniqueConstraint(fields=('user', 'learning_content_key'), name='unique_user_learning_content_key'),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -4,56 +4,25 @@ from django.db.models import UniqueConstraint
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
|
|
||||||
|
|
||||||
class LearningContentCompletion(models.Model):
|
class CircleCompletion(models.Model):
|
||||||
|
# Page can either be a LearningContent or a LearningUnitQuestion for now
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
learning_content_key = models.UUIDField()
|
|
||||||
|
# Page can either be a LearningContent or a LearningUnitQuestion for now
|
||||||
|
page_key = models.UUIDField()
|
||||||
|
page_type = models.CharField(max_length=255, default='', blank=True)
|
||||||
circle_key = models.UUIDField()
|
circle_key = models.UUIDField()
|
||||||
|
|
||||||
completed = models.BooleanField(default=True)
|
completed = models.BooleanField(default=False)
|
||||||
json_data = models.JSONField(default=dict, blank=True)
|
json_data = models.JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
UniqueConstraint(
|
UniqueConstraint(
|
||||||
fields=['user', 'learning_content_key', ],
|
fields=['user', 'page_key', ],
|
||||||
name='unique_user_learning_content_key'
|
name='unique_user_page_key'
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class LearningUnitQuestionCompletion(models.Model):
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
question_key = models.UUIDField()
|
|
||||||
circle_key = models.UUIDField()
|
|
||||||
|
|
||||||
completed = models.BooleanField(default=True)
|
|
||||||
json_data = models.JSONField(default=dict, blank=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
constraints = [
|
|
||||||
UniqueConstraint(
|
|
||||||
fields=['user', 'question_key', ],
|
|
||||||
name='unique_user_question_key'
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class UserCircleCompletion(models.Model):
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
circle_key = models.UUIDField()
|
|
||||||
|
|
||||||
json_data = models.JSONField(default=dict, blank=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
constraints = [
|
|
||||||
UniqueConstraint(fields=['user', 'circle_key'], name='unique_user_circle_completion')
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,13 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from vbv_lernwelt.completion.models import UserCircleCompletion, LearningContentCompletion
|
from vbv_lernwelt.completion.models import CircleCompletion
|
||||||
|
|
||||||
|
|
||||||
class UserCircleCompletionSerializer(serializers.ModelSerializer):
|
class CircleCompletionSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserCircleCompletion
|
model = CircleCompletion
|
||||||
fields = ['id', 'created_at', 'updated_at', 'user', 'circle_key', 'json_data']
|
|
||||||
|
|
||||||
|
|
||||||
class LearningContentCompletionSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = LearningContentCompletion
|
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'created_at', 'updated_at', 'user', 'learning_content_key', 'circle_key',
|
'id', 'created_at', 'updated_at', 'user', 'page_key', 'page_type', 'circle_key',
|
||||||
'completed', 'json_data',
|
'completed', 'json_data',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,30 +26,28 @@ class CompletionApiTestCase(APITestCase):
|
||||||
learning_content_key = str(learning_content.translation_key)
|
learning_content_key = str(learning_content.translation_key)
|
||||||
circle_key = str(learning_content.get_parent().translation_key)
|
circle_key = str(learning_content.get_parent().translation_key)
|
||||||
|
|
||||||
response = self.client.post(f'/api/completion/complete_learning_content/', {
|
mark_url = f'/api/completion/circle/mark/'
|
||||||
'learning_content_key': learning_content_key
|
print(mark_url)
|
||||||
|
|
||||||
|
response = self.client.post(mark_url, {
|
||||||
|
'page_key': learning_content_key,
|
||||||
})
|
})
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
print(json.dumps(response.json(), indent=2))
|
print(json.dumps(response.json(), indent=2))
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response_json['circle_key'], circle_key)
|
self.assertEqual(len(response_json), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(response_json[0]['page_key'], learning_content_key)
|
||||||
response_json['completed_learning_contents'][learning_content_key]['learning_content_key'],
|
self.assertEqual(response_json[0]['circle_key'], circle_key)
|
||||||
learning_content_key
|
self.assertTrue(response_json[0]['completed'])
|
||||||
)
|
|
||||||
|
|
||||||
# test getting the circle data
|
# test getting the circle data
|
||||||
response = self.client.get(f'/api/completion/user_circle_completion/{circle_key}/')
|
response = self.client.get(f'/api/completion/circle/{circle_key}/')
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
print(json.dumps(response.json(), indent=2))
|
print(json.dumps(response.json(), indent=2))
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response_json['circle_key'], circle_key)
|
self.assertEqual(len(response_json), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(response_json[0]['page_key'], learning_content_key)
|
||||||
response_json['completed_learning_contents'][learning_content_key]['learning_content_key'],
|
self.assertEqual(response_json[0]['circle_key'], circle_key)
|
||||||
learning_content_key
|
self.assertTrue(response_json[0]['completed'])
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from vbv_lernwelt.completion.views import complete_learning_content, request_user_circle_completion
|
from vbv_lernwelt.completion.views import request_circle_completion, mark_circle_completion
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(r"circle/<uuid:circle_key>/", request_user_circle_completion, name="request_user_circle_completion"),
|
path(r"circle/<uuid:circle_key>/", request_circle_completion, name="request_circle_completion"),
|
||||||
path(r"complete_learning_content/", complete_learning_content, name="complete_learning_content"),
|
path(r"circle/mark/", mark_circle_completion, name="mark_circle_completion"),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,57 @@
|
||||||
import structlog
|
import structlog
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from wagtail.models import Page
|
||||||
|
|
||||||
from vbv_lernwelt.completion.models import LearningContentCompletion, UserCircleCompletion
|
from vbv_lernwelt.completion.models import CircleCompletion
|
||||||
from vbv_lernwelt.completion.serializers import UserCircleCompletionSerializer, LearningContentCompletionSerializer
|
from vbv_lernwelt.completion.serializers import CircleCompletionSerializer
|
||||||
from vbv_lernwelt.learnpath.models import LearningContent
|
from vbv_lernwelt.learnpath.models import Circle
|
||||||
|
from vbv_lernwelt.learnpath.utils import get_wagtail_type
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
def request_user_circle_completion(request, circle_key):
|
def request_circle_completion(request, circle_key):
|
||||||
ucc = UserCircleCompletion.objects.filter(
|
response_data = CircleCompletionSerializer(
|
||||||
user=request.user,
|
CircleCompletion.objects.filter(user=request.user, circle_key=circle_key),
|
||||||
circle_key=circle_key,
|
many=True,
|
||||||
)
|
|
||||||
|
|
||||||
response_data = {}
|
|
||||||
if ucc.count() > 0:
|
|
||||||
response_data = UserCircleCompletionSerializer(ucc.first()).data
|
|
||||||
|
|
||||||
response_data['completed_learning_contents'] = LearningContentCompletionSerializer(
|
|
||||||
LearningContentCompletion.objects.filter(circle_key=circle_key, user=request.user),
|
|
||||||
many=True
|
|
||||||
).data
|
).data
|
||||||
|
|
||||||
return Response(status=200, data=response_data)
|
return Response(status=200, data=response_data)
|
||||||
|
|
||||||
|
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def complete_learning_content(request):
|
def mark_circle_completion(request):
|
||||||
learning_content_key = request.data.get('learning_content_key')
|
page_key = request.data.get('page_key')
|
||||||
completed = request.data.get('completed', True)
|
completed = request.data.get('completed', True)
|
||||||
learning_content = LearningContent.objects.get(translation_key=learning_content_key)
|
|
||||||
|
|
||||||
circle_key = learning_content.get_parent().translation_key
|
page = Page.objects.get(translation_key=page_key)
|
||||||
|
page_type = get_wagtail_type(page.specific)
|
||||||
|
circle = Circle.objects.ancestor_of(page).first()
|
||||||
|
|
||||||
lcc, created = LearningContentCompletion.objects.get_or_create(
|
cc, created = CircleCompletion.objects.get_or_create(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
learning_content_key=learning_content_key,
|
page_key=page_key,
|
||||||
circle_key=circle_key,
|
circle_key=circle.translation_key,
|
||||||
)
|
)
|
||||||
lcc.completed = completed
|
cc.page_type = page_type
|
||||||
lcc.save()
|
cc.completed = completed
|
||||||
|
cc.save()
|
||||||
|
|
||||||
ucc, created = UserCircleCompletion.objects.get_or_create(
|
response_data = CircleCompletionSerializer(
|
||||||
user=request.user,
|
CircleCompletion.objects.filter(user=request.user, circle_key=circle.translation_key),
|
||||||
circle_key=circle_key,
|
many=True,
|
||||||
)
|
|
||||||
|
|
||||||
response_data = UserCircleCompletionSerializer(ucc).data
|
|
||||||
response_data['completed_learning_contents'] = LearningContentCompletionSerializer(
|
|
||||||
LearningContentCompletion.objects.filter(circle_key=circle_key, user=request.user),
|
|
||||||
many=True
|
|
||||||
).data
|
).data
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'learning content completed',
|
'page completed',
|
||||||
label='completion_api',
|
label='completion_api',
|
||||||
circle_key=circle_key,
|
circle_key=circle.translation_key,
|
||||||
learning_content_key=learning_content_key,
|
circle_title=circle.title,
|
||||||
|
page_key=page_key,
|
||||||
|
page_type=page_type,
|
||||||
|
page_title=page.title,
|
||||||
user_id=request.user.id,
|
user_id=request.user.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 3.2.13 on 2022-06-14 08:51
|
# Generated by Django 3.2.13 on 2022-06-22 15:48
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
@ -61,7 +61,7 @@ class Migration(migrations.Migration):
|
||||||
('contents', wagtail.fields.StreamField([('video', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('web_based_training', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('podcast', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('competence', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('exercise', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('document', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())]))], use_json_field=None)),
|
('contents', wagtail.fields.StreamField([('video', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('web_based_training', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('podcast', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('competence', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('exercise', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('document', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())])), ('knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.TextBlock())]))], use_json_field=None)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Learning Unit',
|
'verbose_name': 'Learning Content',
|
||||||
},
|
},
|
||||||
bases=('wagtailcore.page',),
|
bases=('wagtailcore.page',),
|
||||||
),
|
),
|
||||||
|
|
@ -90,13 +90,22 @@ class Migration(migrations.Migration):
|
||||||
name='LearningUnit',
|
name='LearningUnit',
|
||||||
fields=[
|
fields=[
|
||||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
|
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
|
||||||
('questions', wagtail.fields.StreamField([('question', wagtail.blocks.CharBlock())], use_json_field=True)),
|
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Learning Unit',
|
'verbose_name': 'Learning Unit',
|
||||||
},
|
},
|
||||||
bases=('wagtailcore.page',),
|
bases=('wagtailcore.page',),
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LearningUnitQuestion',
|
||||||
|
fields=[
|
||||||
|
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Learning Unit Question',
|
||||||
|
},
|
||||||
|
bases=('wagtailcore.page',),
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Topic',
|
name='Topic',
|
||||||
fields=[
|
fields=[
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
# Generated by Django 3.2.13 on 2022-06-22 14:04
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('wagtailcore', '0069_log_entry_jsonfield'),
|
|
||||||
('learnpath', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='LearningUnitQuestion',
|
|
||||||
fields=[
|
|
||||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Learning Unit Question',
|
|
||||||
},
|
|
||||||
bases=('wagtailcore.page',),
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='learningcontent',
|
|
||||||
options={'verbose_name': 'Learning Content'},
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='learningunit',
|
|
||||||
name='questions',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import wagtail.api.v2.serializers as wagtail_serializers
|
import wagtail.api.v2.serializers as wagtail_serializers
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
|
|
||||||
|
from vbv_lernwelt.learnpath.utils import get_wagtail_type
|
||||||
|
|
||||||
|
|
||||||
def get_it_serializer_class(model, field_names):
|
def get_it_serializer_class(model, field_names):
|
||||||
return wagtail_serializers.get_serializer_class(model, field_names=field_names, meta_fields=[], base=ItBaseSerializer)
|
return wagtail_serializers.get_serializer_class(model, field_names=field_names, meta_fields=[], base=ItBaseSerializer)
|
||||||
|
|
@ -8,7 +10,7 @@ def get_it_serializer_class(model, field_names):
|
||||||
|
|
||||||
class ItTypeField(wagtail_serializers.TypeField):
|
class ItTypeField(wagtail_serializers.TypeField):
|
||||||
def to_representation(self, obj):
|
def to_representation(self, obj):
|
||||||
name = type(obj)._meta.app_label + '.' + type(obj).__name__
|
name = get_wagtail_type(obj)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,10 +114,6 @@ Fachspezialisten bei.
|
||||||
title="Ich bin in der Lage, mit geeigneten Fragestellungen die Deckung von Versicherungen zu erfassen.",
|
title="Ich bin in der Lage, mit geeigneten Fragestellungen die Deckung von Versicherungen zu erfassen.",
|
||||||
parent=lu
|
parent=lu
|
||||||
)
|
)
|
||||||
LearningUnitQuestionFactory(
|
|
||||||
title="Ich weiss was meine Kunden wollen",
|
|
||||||
parent=lu
|
|
||||||
)
|
|
||||||
LearningContentFactory(
|
LearningContentFactory(
|
||||||
title='Ermittlung des Kundenbedarfs',
|
title='Ermittlung des Kundenbedarfs',
|
||||||
parent=circe_analyse,
|
parent=circe_analyse,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
def get_wagtail_type(obj):
|
||||||
|
return obj._meta.app_label + '.' + type(obj).__name__
|
||||||
Loading…
Reference in New Issue