Add linked models to module edit page

This commit is contained in:
Ramon Wenger 2023-04-12 16:02:49 +02:00
parent 1513a5672c
commit 5ed180ad88
11 changed files with 250 additions and 69 deletions

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.16 on 2023-04-12 14:01
from django.db import migrations
import django.db.models.deletion
import modelcluster.fields
class Migration(migrations.Migration):
dependencies = [
('books', '0042_alter_contentblock_contents'),
('assignments', '0016_alter_assignment_options'),
]
operations = [
migrations.AlterField(
model_name='assignment',
name='module',
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.PROTECT, related_name='assignments', to='books.module'),
),
]

View File

@ -9,6 +9,7 @@ from wagtail.search import index
from core.constants import DEFAULT_RICH_TEXT_FEATURES from core.constants import DEFAULT_RICH_TEXT_FEATURES
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from modelcluster.fields import ParentalKey
@register_snippet @register_snippet
@ -17,53 +18,69 @@ class Assignment(index.Indexed, TimeStampedModel):
assignment = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES) assignment = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES)
solution = RichTextField(null=True, blank=True, features=DEFAULT_RICH_TEXT_FEATURES) solution = RichTextField(null=True, blank=True, features=DEFAULT_RICH_TEXT_FEATURES)
deleted = models.BooleanField(default=False) deleted = models.BooleanField(default=False)
owner = models.ForeignKey(get_user_model(), owner = models.ForeignKey(
on_delete=models.PROTECT, null=True, get_user_model(), on_delete=models.PROTECT, null=True, blank=True
blank=True) # probably don't want to delete all assignments if a user gets deleted ) # probably don't want to delete all assignments if a user gets deleted
module = models.ForeignKey('books.Module', related_name='assignments', on_delete=models.CASCADE) module = ParentalKey(
"books.Module", related_name="assignments", on_delete=models.PROTECT
)
user_created = models.BooleanField(default=False) user_created = models.BooleanField(default=False)
taskbase_id = models.CharField(max_length=255, null=True, blank=True) taskbase_id = models.CharField(max_length=255, null=True, blank=True)
search_fields = [ search_fields = [
index.SearchField('title', partial_match=True), index.SearchField("title", partial_match=True),
index.SearchField('assignment', partial_match=True), index.SearchField("assignment", partial_match=True),
] ]
panels = [ panels = [
FieldPanel('title'), FieldPanel("title"),
FieldPanel('assignment'), FieldPanel("assignment"),
FieldPanel('solution'), FieldPanel("solution"),
FieldPanel('module'), FieldPanel("module"),
AutocompletePanel('owner') AutocompletePanel("owner"),
] ]
def __str__(self): def __str__(self):
return self.title if not self.user_created else '{} (erstellt von {})'.format(self.title, self.owner) return (
self.title
if not self.user_created
else "{} (erstellt von {})".format(self.title, self.owner)
)
class Meta: class Meta:
verbose_name = _('assignment') verbose_name = _("assignment")
verbose_name_plural = _('assignments') verbose_name_plural = _("assignments")
ordering = ['-created'] ordering = ["-created"]
class StudentSubmission(TimeStampedModel): class StudentSubmission(TimeStampedModel):
text = models.TextField(blank=True) text = models.TextField(blank=True)
document = models.URLField(blank=True, default='', max_length=255) document = models.URLField(blank=True, default="", max_length=255)
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE, related_name='submissions') assignment = models.ForeignKey(
student = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='submissions') Assignment, on_delete=models.CASCADE, related_name="submissions"
)
student = models.ForeignKey(
get_user_model(), on_delete=models.CASCADE, related_name="submissions"
)
final = models.BooleanField(default=False) final = models.BooleanField(default=False)
def __str__(self): def __str__(self):
return '{} - {}'.format(self.student.full_name, self.text) return "{} - {}".format(self.student.full_name, self.text)
class SubmissionFeedback(TimeStampedModel): class SubmissionFeedback(TimeStampedModel):
text = models.TextField(blank=True) text = models.TextField(blank=True)
# teacher who created the feedback # teacher who created the feedback
teacher = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='feedbacks') teacher = models.ForeignKey(
student_submission = models.OneToOneField(StudentSubmission, on_delete=models.CASCADE, primary_key=True, get_user_model(), on_delete=models.CASCADE, related_name="feedbacks"
related_name='submission_feedback') )
student_submission = models.OneToOneField(
StudentSubmission,
on_delete=models.CASCADE,
primary_key=True,
related_name="submission_feedback",
)
final = models.BooleanField(default=False) final = models.BooleanField(default=False)
def __str__(self): def __str__(self):
return '{} - {}'.format(self.student_submission.student.full_name, self.text) return "{} - {}".format(self.student_submission.student.full_name, self.text)

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.16 on 2023-04-12 14:01
import books.blocks
from django.db import migrations
import wagtail.blocks
import wagtail.fields
import wagtail.images.blocks
class Migration(migrations.Migration):
dependencies = [
('basicknowledge', '0026_alter_basicknowledge_contents'),
]
operations = [
migrations.AlterField(
model_name='basicknowledge',
name='contents',
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript', 'brand', 'secondary']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('section_title', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock())], blank=True, null=True, use_json_field=True),
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.16 on 2023-04-12 14:01
import assignments.models
import books.blocks
from django.db import migrations
import surveys.models
import wagtail.blocks
import wagtail.documents.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
class Migration(migrations.Migration):
dependencies = [
('books', '0041_alter_contentblock_contents'),
]
operations = [
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript'])), ('document', books.blocks.CMSDocumentBlock(required=False))])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock(required=False)), ('text', wagtail.blocks.TextBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock()), ('content_list_item', wagtail.blocks.StreamBlock([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript']))])), ('basic_knowledge', wagtail.blocks.StructBlock([('description', wagtail.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.blocks.PageChooserBlock(page_type=['basicknowledge.BasicKnowledge'], required=True))])), ('assignment', wagtail.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('solution', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript'])), ('document', books.blocks.CMSDocumentBlock(required=False))])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('infogram_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock()), ('title', wagtail.blocks.TextBlock())])), ('genially_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('thinglink_block', wagtail.blocks.StructBlock([('id', wagtail.blocks.TextBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('instruction', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock(required=False)), ('text', wagtail.blocks.TextBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False))])), ('module_room_slug', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock())])), ('cms_document_block', books.blocks.CMSDocumentBlock())]))], blank=True, null=True, use_json_field=True),
),
]

View File

@ -1,7 +1,8 @@
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList from wagtail.admin.panels import FieldPanel, InlinePanel, TabbedInterface, ObjectList
from wagtail.fields import RichTextField from wagtail.fields import RichTextField
from django.utils.translation import ugettext_lazy as _
from core.constants import DEFAULT_RICH_TEXT_FEATURES from core.constants import DEFAULT_RICH_TEXT_FEATURES
from core.wagtail_utils import StrictHierarchyPage, get_default_settings from core.wagtail_utils import StrictHierarchyPage, get_default_settings
@ -13,8 +14,7 @@ class Module(StrictHierarchyPage):
verbose_name = "Modul" verbose_name = "Modul"
verbose_name_plural = "Module" verbose_name_plural = "Module"
meta_title = models.CharField( meta_title = models.CharField(max_length=255, help_text="e.g. 'Intro' or 'Modul 1'")
max_length=255, help_text="e.g. 'Intro' or 'Modul 1'")
hero_image = models.ForeignKey( hero_image = models.ForeignKey(
"wagtailimages.Image", "wagtailimages.Image",
null=True, null=True,
@ -38,6 +38,26 @@ class Module(StrictHierarchyPage):
FieldPanel("hero_source"), FieldPanel("hero_source"),
FieldPanel("teaser"), FieldPanel("teaser"),
FieldPanel("intro"), FieldPanel("intro"),
InlinePanel(
"assignments",
label=_("Assignment"),
classname="collapsed",
heading=_("linked assignments"),
help_text=_(
"These %s are automatically linked, they are shown here only to provide an overview. Please don't change anything here."
)
% _("assignments"),
),
InlinePanel(
"surveys",
heading=_("linked surveys"),
label=_("Survey"),
classname="collapsed",
help_text=_(
"These %s are automatically linked, they are shown here only to provide an overview. Please don't change anything here."
)
% _("surveys"),
),
] ]
edit_handler = TabbedInterface( edit_handler = TabbedInterface(
@ -92,8 +112,7 @@ class Module(StrictHierarchyPage):
content_block.visible_for.add(school_class_to_sync) content_block.visible_for.add(school_class_to_sync)
for chapter in chapters: for chapter in chapters:
chapter.sync_title_visibility( chapter.sync_title_visibility(school_class_template, school_class_to_sync)
school_class_template, school_class_to_sync)
chapter.sync_description_visibility( chapter.sync_description_visibility(
school_class_template, school_class_to_sync school_class_template, school_class_to_sync
) )
@ -101,8 +120,7 @@ class Module(StrictHierarchyPage):
objective_groups = self.objective_groups.all() objective_groups = self.objective_groups.all()
for objective_group in objective_groups: for objective_group in objective_groups:
objective_group.sync_visibility( objective_group.sync_visibility(school_class_template, school_class_to_sync)
school_class_template, school_class_to_sync)
def get_admin_display_title(self): def get_admin_display_title(self):
return f"{self.meta_title} - {self.title}" return f"{self.meta_title} - {self.title}"

View File

@ -15,21 +15,20 @@ from core.views import override_wagtailadmin_explore_default_ordering
urlpatterns = [ urlpatterns = [
# django admin # django admin
url(r'^guru/', admin.site.urls), url(r"^guru/", admin.site.urls),
url(r'^statistics/', include('statistics.urls', namespace='statistics')), url(r"^statistics/", include("statistics.urls", namespace="statistics")),
# wagtail # wagtail
url(r'^admin/autocomplete/', include(autocomplete_admin_urls)), url(r"^cms/autocomplete/", include(autocomplete_admin_urls)),
re_path(r'^cms/pages/(\d+)/$', override_wagtailadmin_explore_default_ordering), re_path(r"^cms/pages/(\d+)/$", override_wagtailadmin_explore_default_ordering),
url(r'^cms/', include(wagtailadmin_urls)), url(r"^cms/", include(wagtailadmin_urls)),
url(r'^documents/', include(wagtaildocs_urls)), url(r"^documents/", include(wagtaildocs_urls)),
# graphql backend # graphql backend
url(r'^api/', include('api.urls', namespace="api")), url(r"^api/", include("api.urls", namespace="api")),
# favicon # favicon
url(r'^favicon\.ico$', RedirectView.as_view(url='/static/favicon@2x.png', permanent=True)), url(
r"^favicon\.ico$",
RedirectView.as_view(url="/static/favicon@2x.png", permanent=True),
),
] ]
if settings.DEBUG and not settings.USE_AWS: if settings.DEBUG and not settings.USE_AWS:
@ -39,11 +38,13 @@ if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if settings.ENABLE_SILKY: if settings.ENABLE_SILKY:
urlpatterns += [url(r'^silk/', include('silk.urls', namespace='silk'))] urlpatterns += [url(r"^silk/", include("silk.urls", namespace="silk"))]
# actually we use the cms in headless mode but need the url pattern to get the wagtail_serve function # actually we use the cms in headless mode but need the url pattern to get the wagtail_serve function
urlpatterns += [url(r'pages/', include(wagtail_urls)), ] urlpatterns += [
url(r"pages/", include(wagtail_urls)),
]
urlpatterns += [re_path(r'^.*$', views.home, name='home')] urlpatterns += [re_path(r"^.*$", views.home, name="home")]
admin.site.site_header = 'Myskillbox Admin' admin.site.site_header = "Myskillbox Admin"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-09-14 13:57+0000\n" "POT-Creation-Date: 2023-04-12 13:50+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,43 +18,80 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: assignments/models.py:44 #: assignments/models.py:51
msgid "assignment" msgid "assignment"
msgstr "auftrag" msgstr "Auftrag"
#: assignments/models.py:45 #: assignments/models.py:52 books/models/module.py:49
msgid "assignments" msgid "assignments"
msgstr "aufträge" msgstr "Aufträge"
#: basicknowledge/models.py:31 #: basicknowledge/models.py:40
msgid "instrument categories" msgid "instrument categories"
msgstr "instrumentkategorien" msgstr "instrumentkategorien"
#: basicknowledge/models.py:32 #: basicknowledge/models.py:41
msgid "instrument category" msgid "instrument category"
msgstr "instrumentkategorie" msgstr "instrumentkategorie"
#: basicknowledge/models.py:41 #: basicknowledge/models.py:50
#, fuzzy
#| msgid "instrument category"
msgid "instrument type"
msgstr "instrumenttyp"
#: basicknowledge/models.py:51
msgid "instrument types" msgid "instrument types"
msgstr "instrumenttypen" msgstr "instrumenttypen"
#: basicknowledge/models.py:69 #: basicknowledge/models.py:78
msgid "instrument" msgid "instrument"
msgstr "instrument" msgstr "instrument"
#: basicknowledge/models.py:70 #: basicknowledge/models.py:79
msgid "instruments" msgid "instruments"
msgstr "instrumente" msgstr "instrumente"
#: basicknowledge/wagtail_hooks.py:25 #: basicknowledge/wagtail_hooks.py:41
msgid "Instruments" msgid "Instruments"
msgstr "Instrumente" msgstr "Instrumente"
#: core/settings.py:197 #: books/models/module.py:43
msgid "Assignment"
msgstr "Auftrag"
#: books/models/module.py:45
#| msgid "assignments"
msgid "linked assignments"
msgstr "Verlinkte Aufträge"
#: books/models/module.py:47 books/models/module.py:57
#, python-format
msgid ""
"These %s are automatically linked, they are shown here only to provide an "
"overview. Please don't change anything here."
msgstr ""
"Diese %s sind automatisch verlinkt, sie werden hier als Übersicht angezeigt. "
"Bitte hier nichts verändern."
#: books/models/module.py:53
#| msgid "surveys"
msgid "linked surveys"
msgstr "Verlinkte Surveys"
#: books/models/module.py:54
msgid "Survey"
msgstr "Survey"
#: books/models/module.py:59
msgid "surveys"
msgstr "Übungen"
#: core/settings.py:193
msgid "German" msgid "German"
msgstr "" msgstr ""
#: core/settings.py:198 #: core/settings.py:194
msgid "English" msgid "English"
msgstr "" msgstr ""
@ -330,10 +367,8 @@ msgstr ""
msgid "Das alte Passwort muss angegeben werden" msgid "Das alte Passwort muss angegeben werden"
msgstr "" msgstr ""
#, fuzzy #~ msgid "Assignments"
#~| msgid "instrument category" #~ msgstr "Aufträge"
#~ msgid "instrument type"
#~ msgstr "instrumenttyp"
#~ msgid "assignmentsss" #~ msgid "Surveys"
#~ msgstr "aufträge" #~ msgstr "Surveys"

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.16 on 2023-04-12 14:01
from django.db import migrations
import wagtail.blocks
import wagtail.fields
class Migration(migrations.Migration):
dependencies = [
('rooms', '0013_alter_roomentry_contents'),
]
operations = [
migrations.AlterField(
model_name='roomentry',
name='contents',
field=wagtail.fields.StreamField([('text_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.RichTextBlock(features=['ul', 'bold', 'subscript', 'superscript']))])), ('image_url_block', wagtail.blocks.StructBlock([('title', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('link_block', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock()), ('url', wagtail.blocks.URLBlock())])), ('document_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())])), ('subtitle', wagtail.blocks.StructBlock([('text', wagtail.blocks.TextBlock())])), ('video_block', wagtail.blocks.StructBlock([('url', wagtail.blocks.URLBlock())]))], blank=True, null=True, use_json_field=True),
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.16 on 2023-04-12 14:01
from django.db import migrations
import django.db.models.deletion
import modelcluster.fields
class Migration(migrations.Migration):
dependencies = [
('books', '0042_alter_contentblock_contents'),
('surveys', '0006_auto_20230214_1415'),
]
operations = [
migrations.AlterField(
model_name='survey',
name='module',
field=modelcluster.fields.ParentalKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='surveys', to='books.module'),
),
]

View File

@ -3,12 +3,13 @@ from django.db import models
from django.db.models import JSONField from django.db.models import JSONField
from wagtail.snippets.models import register_snippet from wagtail.snippets.models import register_snippet
from wagtail.search import index from wagtail.search import index
from modelcluster.fields import ParentalKey
@register_snippet @register_snippet
class Survey(models.Model, index.Indexed): class Survey(models.Model, index.Indexed):
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
module = models.ForeignKey( module = ParentalKey(
"books.Module", "books.Module",
related_name="surveys", related_name="surveys",
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -34,8 +35,7 @@ class Answer(models.Model):
get_user_model(), on_delete=models.CASCADE, related_name="answers" get_user_model(), on_delete=models.CASCADE, related_name="answers"
) )
data = JSONField() data = JSONField()
survey = models.ForeignKey( survey = models.ForeignKey(Survey, on_delete=models.PROTECT, related_name="answers")
Survey, on_delete=models.PROTECT, related_name="answers")
def __str__(self): def __str__(self):
return "{} - {}".format(self.owner.username, self.survey.title) return "{} - {}".format(self.owner.username, self.survey.title)