Skip to content

Commit

Permalink
Merge branch 'development' of https://github.com/breatheco-de/apiv2 i…
Browse files Browse the repository at this point in the history
…nto development
  • Loading branch information
jefer94 committed May 23, 2024
2 parents c3008c5 + 7dd81f1 commit 070392e
Show file tree
Hide file tree
Showing 30 changed files with 2,103 additions and 646 deletions.
932 changes: 457 additions & 475 deletions Pipfile.lock

Large diffs are not rendered by default.

145 changes: 118 additions & 27 deletions breathecode/assessment/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,140 @@
logger = logging.getLogger(__name__)


def create_from_asset(asset):
def validate_quiz_json(quiz, allow_override=False):

if 'info' not in quiz:
raise ValidationException(f'Quiz is missing info json property')

if 'slug' not in quiz['info']:
raise ValidationException(f'Missing info.slug on quiz info')

_result = {'questions': []}

# We guarantee that "assessment" property will always be set to something (none or else)
_result['assessment'] = Assessment.objects.filter(slug=quiz['info']['slug']).first()
if not allow_override and _result['assessment']:
raise ValidationException(
f"There is already an assessment (maybe it's archived) with slug {quiz['info']['slug']}, rename your quiz info.slug or delete the previous assessment"
)

if 'id' in quiz: _result['id'] = quiz['id']
elif 'id' in quiz['info']: _result['id'] = quiz['info']['id']

if 'questions' not in quiz:
raise Exception(f'Missing "questions" property in quiz')

title = 'Untitled assessment'
if 'name' in quiz['info']: title = quiz['info']['name']
if 'title' in quiz['info']: title = quiz['info']['title']

_result['info'] = {
'title': title,
'slug': quiz['info']['slug'],
}

_index = 0
for question in quiz['questions']:
_index += 1

_question = {'id': question['id'] if 'id' in question else None}

title = ''
if 'q' in question: title = question['q']
elif 'title' in question: title = question['title']
else: raise Exception(f'Missing "title" property in quiz question #{_index}')

_question['title'] = title

options = []
if 'a' in question: options = question['a']
elif 'answers' in question: options = question['answers']
elif 'options' in question: options = question['options']
else: raise Exception('Missing "options" property in quiz question')

_question['options'] = []

o_index = 0
for option in options:
o_index += 1

_id = None
if 'id' in option:
_id = option['id']

title = 'Untitled option'
if 'option' in option: title = option['option']
elif 'title' in option: title = option['title']
else: raise Exception(f'Missing "title" property in option {str(o_index)}')

score = 0
if 'correct' in option: score = option['correct']
elif 'score' in option: score = option['score']
else: raise Exception(f'Missing "score" property in option {str(o_index)}')

_question['options'].append({'id': _id, 'title': title, 'score': int(score)})

_result['questions'].append(_question)

return _result


def create_from_asset(asset, allow_override=False):

if asset.academy is None:
raise ValidationException(f'Asset {asset.slug} has not academy associated')

a = asset.assessment

if asset.assessment is not None and asset.assessment.asset_set.count() > 1:
associated_assets = ','.join(asset.assessment.asset_set.all())
raise ValidationException('Assessment has more then one asset associated, please choose only one: ' +
associated_assets)

quiz = validate_quiz_json(asset.config, allow_override)
if asset.assessment is None:
a = Assessment.objects.filter(slug=asset.slug).first()
if a is not None:
raise ValidationException(f'There is already an assessment with slug {asset.slug}')

a = Assessment.objects.create(title=asset.title,
lang=asset.lang,
slug=asset.slug,
academy=asset.academy,
author=asset.author)

if a.question_set.count() > 0:
a = quiz['assessment']
if not a:
a = Assessment.objects.create(title=quiz['info']['title'],
lang=asset.lang,
slug=quiz['info']['slug'],
academy=asset.academy,
author=asset.author)

if a is not None and a.question_set is not None and a.question_set.count() > 0:
raise ValidationException(
'Assessment already has questions, only empty assessments can by created from an asset')

a.save()
quiz = asset.config

for question in quiz['questions']:
q = Question(
title=question['q'],
lang=asset.lang,
assessment=a,
question_type='SELECT',
)
q.save()
for option in question['a']:
o = Option(
title=option['option'],
score=int(option['correct']),
question=q,

q = None
if question['id']:
q = Question.filter(id=question['id']).first()
if not q:
raise ValidationException(f"Question with id {question['id']} not found for quiz {q.id}")

if not q:
q = Question(
lang=asset.lang,
assessment=a,
question_type='SELECT',
)

q.title = question['title']
q.save()

for option in question['options']:
o = None
if option['id']:
o = Option.filter(id=option['id']).first()
if not o:
raise ValidationException(f"Option with id {option['id']} not found for question {q.id}")

if not o:
o = Option(question=q)

o.title = option['title']
o.score = option['score']
o.save()

asset.assessment = a
Expand Down
60 changes: 51 additions & 9 deletions breathecode/assessment/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import logging
import re
from django.contrib import admin, messages
from django.contrib.auth.admin import UserAdmin
from .models import (Assessment, UserAssessment, UserProxy, Question, Option, AssessmentThreshold, Answer)
from breathecode.utils.admin import change_field
from django.utils.html import format_html
from .models import (Assessment, UserAssessment, UserProxy, Question, Option, AssessmentThreshold, Answer,
AssessmentLayout)
from .actions import send_assestment

logger = logging.getLogger(__name__)
Expand All @@ -22,7 +26,6 @@ def send_bulk_assesment(modeladmin, request, queryset):
@admin.register(UserProxy)
class UserAdmin(UserAdmin):
list_display = ('username', 'email', 'first_name', 'last_name')
actions = [send_bulk_assesment]


# Register your models here.
Expand All @@ -47,27 +50,66 @@ class QuestionAdmin(admin.ModelAdmin):
@admin.register(Option)
class OptionAdmin(admin.ModelAdmin):
search_fields = ['title', 'question__assessment__title']
list_display = ['title', 'is_deleted', 'position', 'lang', 'score', 'question']
list_display = ['id', 'title', 'is_deleted', 'position', 'lang', 'score', 'question']
list_filter = ['lang', 'is_deleted']


# Register your models here.
def change_status_ANSWERED(modeladmin, request, queryset):
items = queryset.all()
for i in items:
i.status = 'ANSWERED'
i.save()


@admin.register(UserAssessment)
class UserAssessmentAdmin(admin.ModelAdmin):
search_fields = ['title', 'question__assessment__title']
list_display = ['title', 'status', 'lang', 'owner', 'total_score', 'assessment']
list_filter = ['lang']
readonly_fields = ('token', )
list_display = ['id', 'title', 'current_status', 'lang', 'owner', 'total_score', 'assessment', 'academy']
list_filter = ['lang', 'status', 'academy']
actions = [change_status_ANSWERED] + change_field(['DRAFT', 'SENT', 'ERROR', 'EXPIRED'], name='status')

def current_status(self, obj):
colors = {
'DRAFT': 'bg-secondary',
'SENT': 'bg-warning',
'ANSWERED': 'bg-success',
'ERROR': 'bg-error',
'EXPIRED': 'bg-warning',
None: 'bg-error',
}

def from_status(s):
if s in colors:
return colors[s]
return ''

status = 'No status'
if obj.status_text is not None:
status = re.sub(r'[^\w\._\-]', ' ', obj.status_text)
return format_html(f"""<table style='max-width: 200px;'>
<td><span class='badge {from_status(obj.status)}'>{obj.status}</span></td>
<tr><td>{status}</td></tr>
</table>""")


@admin.register(AssessmentThreshold)
class UserAssessmentThresholdAdmin(admin.ModelAdmin):
search_fields = ['assessment__slug', 'assessment__title']
list_display = ['id', 'score_threshold', 'assessment']
list_filter = ['assessment__slug']
actions = []


@admin.register(Answer)
class AnswerAdmin(admin.ModelAdmin):
search_fields = ['user_assesment__owner', 'user_assesment__title']
list_display = ['id', 'question', 'option', 'value']
list_filter = ['user_assesment__assessment__slug']
search_fields = ['user_assessment__owner', 'user_assessment__title']
list_display = ['id', 'user_assessment', 'question', 'option', 'value']
list_filter = ['user_assessment__assessment__slug']


@admin.register(AssessmentLayout)
class AssessmentLayoutAdmin(admin.ModelAdmin):
search_fields = ['slug']
list_display = ['id', 'slug', 'academy']
list_filter = ['academy']
7 changes: 7 additions & 0 deletions breathecode/assessment/apps.py
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
from django.apps import AppConfig # noqa: F401


class AssessmentConfig(AppConfig):
name = 'breathecode.assessment'

def ready(self):
from . import receivers # noqa: F401
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import User
from django.utils import timezone
from breathecode.admissions.models import CohortUser
from django.db.models import Count

HOST = os.environ.get('OLD_BREATHECODE_API')
DATETIME_FORMAT = '%Y-%m-%d'


class Command(BaseCommand):
help = 'Close user assessments and totalize scores'

def handle(self, *args, **options):

unfinished_ua = UserAssessment.objects.filter(finished_at__isnull=True, status='SENT')
total = unfinished_ua.count()
unfinished_ua.update(status='ERROR', status_text='Unfinished user assessment')
self.stdout.write(self.style.SUCCESS(f'{total} user assessments automatically closed with error'))
Loading

0 comments on commit 070392e

Please sign in to comment.