Compare commits
5 Commits
80dca6def3
...
e4b58b286d
| Author | SHA1 | Date |
|---|---|---|
|
|
e4b58b286d | |
|
|
568cfb507e | |
|
|
6364fc8374 | |
|
|
3533f776bd | |
|
|
e66c79f5fa |
1
Pipfile
1
Pipfile
|
|
@ -47,3 +47,4 @@ black = "*"
|
||||||
pytest-django = "*"
|
pytest-django = "*"
|
||||||
pytest-xdist = "*"
|
pytest-xdist = "*"
|
||||||
pytest-cov = "*"
|
pytest-cov = "*"
|
||||||
|
django-redis = "*"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -38,6 +38,7 @@ describe('Instruments on Module page', () => {
|
||||||
value: {
|
value: {
|
||||||
description:
|
description:
|
||||||
'<p>Ein Interview dient dazu, durch Befragung Informationen zu ermitteln. Bei journalistischen Interviews werden oft Expertinnen und Experten befragt, aber auch Personen.</p>',
|
'<p>Ein Interview dient dazu, durch Befragung Informationen zu ermitteln. Bei journalistischen Interviews werden oft Expertinnen und Experten befragt, aber auch Personen.</p>',
|
||||||
|
slug: 'interview',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineAsyncComponent, inject, onMounted, ref, computed, onUnmounted } from 'vue';
|
import { inject, onMounted, ref, computed, onUnmounted } from 'vue';
|
||||||
|
|
||||||
import { useMutation } from '@vue/apollo-composable';
|
import { useMutation } from '@vue/apollo-composable';
|
||||||
import AddContentButton from '@/components/AddContentButton.vue';
|
import AddContentButton from '@/components/AddContentButton.vue';
|
||||||
|
|
@ -109,6 +109,7 @@ import UserWidget from '@/components/UserWidget.vue';
|
||||||
import VisibilityAction from '@/components/visibility/VisibilityAction.vue';
|
import VisibilityAction from '@/components/visibility/VisibilityAction.vue';
|
||||||
import PopoverLink from '@/components/ui/PopoverLink.vue';
|
import PopoverLink from '@/components/ui/PopoverLink.vue';
|
||||||
import CopyLink from '@/components/CopyLink.vue';
|
import CopyLink from '@/components/CopyLink.vue';
|
||||||
|
import ContentComponent from '@/components/content-blocks/ContentComponent.vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { hidden } from '@/helpers/visibility';
|
import { hidden } from '@/helpers/visibility';
|
||||||
import { insertAtIndex, removeAtIndex } from '@/graphql/immutable-operations';
|
import { insertAtIndex, removeAtIndex } from '@/graphql/immutable-operations';
|
||||||
|
|
@ -144,8 +145,6 @@ export interface Props {
|
||||||
editMode?: boolean;
|
editMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentComponent = defineAsyncComponent(() => import('@/components/content-blocks/ContentComponent.vue'));
|
|
||||||
|
|
||||||
const { me, schoolClass } = getMe();
|
const { me, schoolClass } = getMe();
|
||||||
|
|
||||||
const contentBlockDiv = ref<HTMLElement | null>(null);
|
const contentBlockDiv = ref<HTMLElement | null>(null);
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,9 @@ export interface Props {
|
||||||
saved: boolean;
|
saved: boolean;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
action: string;
|
action: string;
|
||||||
readOnly: boolean;
|
readOnly?: boolean;
|
||||||
spellcheck: boolean;
|
spellcheck?: boolean;
|
||||||
spellcheckLoading: boolean;
|
spellcheckLoading?: boolean;
|
||||||
sharedMsg: string;
|
sharedMsg: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,11 @@ const editText = ref(props.inputText);
|
||||||
const emit = defineEmits(['input']);
|
const emit = defineEmits(['input']);
|
||||||
|
|
||||||
const containsEmoji = (text: string) => {
|
const containsEmoji = (text: string) => {
|
||||||
|
if (!text) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
const lastCharacter = Array.from(text)[Array.from(text).length - 1];
|
const lastCharacter = Array.from(text)[Array.from(text).length - 1];
|
||||||
|
|
||||||
if (lastCharacter && lastCharacter.charCodeAt(0)> 55000) //fix for the hand symbol 🖐️
|
if (lastCharacter && lastCharacter.charCodeAt(0)> 55000) //fix for the hand symbol 🖐️
|
||||||
return true
|
return true
|
||||||
const emojiRegex = /\p{Emoji}/u;
|
const emojiRegex = /\p{Emoji}/u;
|
||||||
|
|
@ -59,11 +63,9 @@ watch(
|
||||||
() => props.inputText,
|
() => props.inputText,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
// TODO: Lorenz this is an ugly fix!
|
// TODO: Lorenz this is an ugly fix!
|
||||||
console.log(newValue)
|
|
||||||
|
|
||||||
if (containsEmoji(newValue)) {
|
if (containsEmoji(newValue)) {
|
||||||
console.log("emoji found", newValue)
|
|
||||||
editText.value = newValue;
|
editText.value = newValue;
|
||||||
|
emit('input', editText.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,3 @@
|
||||||
from django.db import models
|
|
||||||
from django.utils.text import slugify
|
|
||||||
from wagtail.admin.panels import FieldPanel, TitleFieldPanel
|
|
||||||
from wagtail.fields import RichTextField, StreamField
|
|
||||||
from wagtail.images.blocks import ImageChooserBlock
|
|
||||||
|
|
||||||
from books.blocks import (
|
from books.blocks import (
|
||||||
CMSDocumentBlock,
|
CMSDocumentBlock,
|
||||||
DocumentBlock,
|
DocumentBlock,
|
||||||
|
|
@ -18,7 +12,12 @@ from books.blocks import (
|
||||||
)
|
)
|
||||||
from core.constants import DEFAULT_RICH_TEXT_FEATURES
|
from core.constants import DEFAULT_RICH_TEXT_FEATURES
|
||||||
from core.wagtail_utils import StrictHierarchyPage
|
from core.wagtail_utils import StrictHierarchyPage
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.text import slugify
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from wagtail.admin.panels import FieldPanel, TitleFieldPanel
|
||||||
|
from wagtail.fields import RichTextField, StreamField
|
||||||
|
from wagtail.images.blocks import ImageChooserBlock
|
||||||
|
|
||||||
LANGUAGE_COMMUNICATION = "language_communication"
|
LANGUAGE_COMMUNICATION = "language_communication"
|
||||||
SOCIETY = "society"
|
SOCIETY = "society"
|
||||||
|
|
@ -77,6 +76,7 @@ class BasicKnowledge(StrictHierarchyPage):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("instrument")
|
verbose_name = _("instrument")
|
||||||
verbose_name_plural = _("instruments")
|
verbose_name_plural = _("instruments")
|
||||||
|
ordering = ["path"]
|
||||||
|
|
||||||
parent_page_types = ["books.book"]
|
parent_page_types = ["books.book"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList, TitleFieldPanel
|
|
||||||
|
|
||||||
from core.wagtail_utils import StrictHierarchyPage, get_default_settings
|
|
||||||
from users.models import SchoolClass
|
|
||||||
from core.mixins import GraphqlNodeMixin
|
from core.mixins import GraphqlNodeMixin
|
||||||
|
from core.wagtail_utils import StrictHierarchyPage, get_default_settings
|
||||||
|
from django.db import models
|
||||||
|
from users.models import SchoolClass
|
||||||
|
from wagtail.admin.panels import (
|
||||||
|
FieldPanel,
|
||||||
|
ObjectList,
|
||||||
|
TabbedInterface,
|
||||||
|
TitleFieldPanel,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -14,6 +18,7 @@ class Chapter(StrictHierarchyPage, GraphqlNodeMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Kapitel"
|
verbose_name = "Kapitel"
|
||||||
verbose_name_plural = "Kapitel"
|
verbose_name_plural = "Kapitel"
|
||||||
|
ordering = ["path"]
|
||||||
|
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ class Module(StrictHierarchyPage):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Modul"
|
verbose_name = "Modul"
|
||||||
verbose_name_plural = "Module"
|
verbose_name_plural = "Module"
|
||||||
|
ordering = ["path"]
|
||||||
|
|
||||||
meta_title = models.CharField(max_length=255, help_text="e.g. 'Intro' or 'Modul 1'")
|
meta_title = models.CharField(max_length=255, help_text="e.g. 'Intro' or 'Modul 1'")
|
||||||
level = models.ForeignKey(
|
level = models.ForeignKey(
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ class ContentBlockBookmark(Bookmark):
|
||||||
name="unique_content_bookmark_per_user",
|
name="unique_content_bookmark_per_user",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
ordering = ["pk"]
|
||||||
|
|
||||||
|
|
||||||
class ModuleBookmark(Bookmark):
|
class ModuleBookmark(Bookmark):
|
||||||
|
|
@ -35,6 +36,9 @@ class ModuleBookmark(Bookmark):
|
||||||
|
|
||||||
|
|
||||||
class ChapterBookmark(Bookmark):
|
class ChapterBookmark(Bookmark):
|
||||||
|
class Meta:
|
||||||
|
ordering = ["pk"]
|
||||||
|
|
||||||
chapter = models.ForeignKey("books.Chapter", on_delete=models.CASCADE)
|
chapter = models.ForeignKey("books.Chapter", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,48 @@
|
||||||
|
from books.models import Module
|
||||||
|
from core.utils import sync_hidden_for, sync_visible_for
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from books.models import Module
|
|
||||||
from core.utils import sync_visible_for, sync_hidden_for
|
|
||||||
from users.models import SchoolClass
|
from users.models import SchoolClass
|
||||||
|
|
||||||
|
|
||||||
class ObjectiveGroup(models.Model):
|
class ObjectiveGroup(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Lernzielgruppe'
|
verbose_name = "Lernzielgruppe"
|
||||||
verbose_name_plural = 'Lernzielgruppen'
|
verbose_name_plural = "Lernzielgruppen"
|
||||||
|
ordering = ["pk"]
|
||||||
|
|
||||||
LANGUAGE_COMMUNICATION = 'language_communication'
|
LANGUAGE_COMMUNICATION = "language_communication"
|
||||||
SOCIETY = 'society'
|
SOCIETY = "society"
|
||||||
INTERDISCIPLINARY = 'interdisciplinary'
|
INTERDISCIPLINARY = "interdisciplinary"
|
||||||
|
|
||||||
TITLE_CHOICES = (
|
TITLE_CHOICES = (
|
||||||
(LANGUAGE_COMMUNICATION, 'Sprache & Kommunikation'),
|
(LANGUAGE_COMMUNICATION, "Sprache & Kommunikation"),
|
||||||
(SOCIETY, 'Gesellschaft'),
|
(SOCIETY, "Gesellschaft"),
|
||||||
(INTERDISCIPLINARY, 'Überfachliche Lernziele'),
|
(INTERDISCIPLINARY, "Überfachliche Lernziele"),
|
||||||
)
|
)
|
||||||
|
|
||||||
title = models.CharField('title', blank=True, null=False, max_length=255, choices=TITLE_CHOICES,
|
title = models.CharField(
|
||||||
default=LANGUAGE_COMMUNICATION)
|
"title",
|
||||||
module = models.ForeignKey(Module, blank=False, null=False, on_delete=models.CASCADE,
|
blank=True,
|
||||||
related_name='objective_groups')
|
null=False,
|
||||||
|
max_length=255,
|
||||||
|
choices=TITLE_CHOICES,
|
||||||
|
default=LANGUAGE_COMMUNICATION,
|
||||||
|
)
|
||||||
|
module = models.ForeignKey(
|
||||||
|
Module,
|
||||||
|
blank=False,
|
||||||
|
null=False,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="objective_groups",
|
||||||
|
)
|
||||||
|
|
||||||
hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_objective_groups', blank=True)
|
hidden_for = models.ManyToManyField(
|
||||||
|
SchoolClass, related_name="hidden_objective_groups", blank=True
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{} - {}'.format(self.module, self.title)
|
return "{} - {}".format(self.module, self.title)
|
||||||
|
|
||||||
def sync_visibility(self, school_class_template, school_class_to_sync):
|
def sync_visibility(self, school_class_template, school_class_to_sync):
|
||||||
# if self.hidden_for.filter(id=school_class_template.id).exists() and not self.hidden_for.filter(id=school_class_to_sync.id).exists():
|
# if self.hidden_for.filter(id=school_class_template.id).exists() and not self.hidden_for.filter(id=school_class_to_sync.id).exists():
|
||||||
|
|
@ -45,21 +58,32 @@ class ObjectiveGroup(models.Model):
|
||||||
|
|
||||||
class Objective(models.Model):
|
class Objective(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Lernziel'
|
verbose_name = "Lernziel"
|
||||||
verbose_name_plural = 'Lernziele'
|
verbose_name_plural = "Lernziele"
|
||||||
# todo: reinstate ordering in resolver
|
# todo: reinstate ordering in resolver
|
||||||
# ordering = [F('owner').asc(nulls_first=True), F('order').asc(nulls_last=True)]
|
# ordering = [F('owner').asc(nulls_first=True), F('order').asc(nulls_last=True)]
|
||||||
|
|
||||||
text = models.CharField('text', blank=True, null=False, max_length=255)
|
text = models.CharField("text", blank=True, null=False, max_length=255)
|
||||||
group = models.ForeignKey(ObjectiveGroup, blank=False, null=False, on_delete=models.CASCADE,
|
group = models.ForeignKey(
|
||||||
related_name='objectives')
|
ObjectiveGroup,
|
||||||
owner = models.ForeignKey(get_user_model(), blank=True, null=True, on_delete=models.PROTECT)
|
blank=False,
|
||||||
hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_objectives', blank=True)
|
null=False,
|
||||||
visible_for = models.ManyToManyField(SchoolClass, related_name='visible_objectives', blank=True)
|
on_delete=models.CASCADE,
|
||||||
|
related_name="objectives",
|
||||||
|
)
|
||||||
|
owner = models.ForeignKey(
|
||||||
|
get_user_model(), blank=True, null=True, on_delete=models.PROTECT
|
||||||
|
)
|
||||||
|
hidden_for = models.ManyToManyField(
|
||||||
|
SchoolClass, related_name="hidden_objectives", blank=True
|
||||||
|
)
|
||||||
|
visible_for = models.ManyToManyField(
|
||||||
|
SchoolClass, related_name="visible_objectives", blank=True
|
||||||
|
)
|
||||||
order = models.IntegerField(null=True, blank=True)
|
order = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'Objective {}-{}'.format(self.id, self.text)
|
return "Objective {}-{}".format(self.id, self.text)
|
||||||
|
|
||||||
def sync_visibility(self, school_class_template, school_class_to_sync):
|
def sync_visibility(self, school_class_template, school_class_to_sync):
|
||||||
sync_hidden_for(self, school_class_template, school_class_to_sync)
|
sync_hidden_for(self, school_class_template, school_class_to_sync)
|
||||||
|
|
@ -67,27 +91,25 @@ class Objective(models.Model):
|
||||||
|
|
||||||
def is_hidden_for_class(self, school_class):
|
def is_hidden_for_class(self, school_class):
|
||||||
return (
|
return (
|
||||||
self.owner is None and self.hidden_for.filter(id=school_class.id).exists()
|
self.owner is None and self.hidden_for.filter(id=school_class.id).exists()
|
||||||
) or (
|
) or (
|
||||||
self.owner is not None and not self.visible_for.filter(id=school_class.id).exists()
|
self.owner is not None
|
||||||
)
|
and not self.visible_for.filter(id=school_class.id).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ObjectiveSnapshot(Objective):
|
class ObjectiveSnapshot(Objective):
|
||||||
hidden = models.BooleanField(default=False)
|
hidden = models.BooleanField(default=False)
|
||||||
snapshot = models.ForeignKey(
|
snapshot = models.ForeignKey(
|
||||||
'books.Snapshot',
|
"books.Snapshot",
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
related_name='custom_objectives'
|
related_name="custom_objectives",
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_regular_objective(self, owner, school_class):
|
def to_regular_objective(self, owner, school_class):
|
||||||
objective = Objective.objects.create(
|
objective = Objective.objects.create(
|
||||||
owner=owner,
|
owner=owner, text=self.text, group=self.group, order=self.order
|
||||||
text=self.text,
|
|
||||||
group=self.group,
|
|
||||||
order=self.order
|
|
||||||
)
|
)
|
||||||
|
|
||||||
objective.visible_for.add(school_class)
|
objective.visible_for.add(school_class)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.db import models
|
|
||||||
from django_extensions.db.models import TitleSlugDescriptionModel
|
|
||||||
from wagtail.fields import StreamField
|
|
||||||
|
|
||||||
from books.blocks import (
|
from books.blocks import (
|
||||||
DocumentBlock,
|
DocumentBlock,
|
||||||
ImageUrlBlock,
|
ImageUrlBlock,
|
||||||
|
|
@ -12,7 +7,11 @@ from books.blocks import (
|
||||||
)
|
)
|
||||||
from books.models import TextBlock
|
from books.models import TextBlock
|
||||||
from core.mixins import GraphqlNodeMixin
|
from core.mixins import GraphqlNodeMixin
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.db import models
|
||||||
|
from django_extensions.db.models import TitleSlugDescriptionModel
|
||||||
from users.models import SchoolClass
|
from users.models import SchoolClass
|
||||||
|
from wagtail.fields import StreamField
|
||||||
|
|
||||||
|
|
||||||
class Room(TitleSlugDescriptionModel, GraphqlNodeMixin):
|
class Room(TitleSlugDescriptionModel, GraphqlNodeMixin):
|
||||||
|
|
@ -39,6 +38,7 @@ class RoomEntry(TitleSlugDescriptionModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Raumeintrag"
|
verbose_name = "Raumeintrag"
|
||||||
verbose_name_plural = "Raumeinträge"
|
verbose_name_plural = "Raumeinträge"
|
||||||
|
ordering = ["pk"]
|
||||||
|
|
||||||
room = models.ForeignKey(
|
room = models.ForeignKey(
|
||||||
Room,
|
Room,
|
||||||
|
|
@ -47,8 +47,7 @@ class RoomEntry(TitleSlugDescriptionModel):
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="room_entries",
|
related_name="room_entries",
|
||||||
)
|
)
|
||||||
author = models.ForeignKey(
|
author = models.ForeignKey(get_user_model(), null=True, on_delete=models.CASCADE)
|
||||||
get_user_model(), null=True, on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
contents = StreamField(
|
contents = StreamField(
|
||||||
[
|
[
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue