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 feat/google-meet
  • Loading branch information
jefer94 committed Jun 14, 2024
2 parents 8b3667b + 2952706 commit 42246c9
Show file tree
Hide file tree
Showing 93 changed files with 5,214 additions and 1,695 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
ENV=development
LOG_LEVEL=DEBUG
DATABASE_URL=postgres://gitpod@localhost:5432/breathecode
CACHE=0
CACHE_MIDDLEWARE_MINUTES=15
SECURE_SSL_REDIRECT=FALSE
APPLY_CAPTCHA=FALSE

# URLS
API_URL=https://breathecode.herokuapp.com
Expand Down
19 changes: 9 additions & 10 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ env:
APP_URL: https://4geeks.com

# cache ------------------------------------
# | |> migrations --------
# | |> migrations --------
# |> undefined-and-unused-variables |> dependencies ------
# |> bad-docstrings |
# |> code-complexity |> tests -------|> dockerhub
# |> naming-conventions |> linter
# |> unexpected-behaviors |> pages
# | |> dependencies
# |> undefined-and-unused-variables |> migrations
# |> bad-docstrings |
# |> code-complexity |> tests -------|> dockerhub
# |> naming-conventions |> linter
# |> unexpected-behaviors |> pages


jobs:
Expand Down Expand Up @@ -318,7 +317,7 @@ jobs:
run: pipenv run flake8 --select=B

tests:
needs: [migrations, dependencies]
needs: [migrations]
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -370,7 +369,7 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}

linter:
needs: [migrations, dependencies]
needs: [migrations]
runs-on: ubuntu-latest
continue-on-error: true

Expand Down Expand Up @@ -406,7 +405,7 @@ jobs:
pipenv run format
pages:
needs: [migrations, dependencies]
needs: [migrations]
if: >-
github.repository == 'breatheco-de/apiv2' &&
github.event_name == 'push' &&
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,5 @@ dump.rdb
*:Zone.Identifier

node_modules/
.venv*
.env*
4 changes: 2 additions & 2 deletions .gitpod.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ RUN sudo apt-get update \
# RUN curl https://pyenv.run | bash


# RUN pyenv update && pyenv install 3.12.3 && pyenv global 3.12.3
RUN pyenv install 3.12.3 && pyenv global 3.12.3
# RUN pyenv update && pyenv install 3.12.2 && pyenv global 3.12.2
RUN pyenv install 3.12.2 && pyenv global 3.12.2
RUN pip install pipenv yapf

# remove PIP_USER environment
Expand Down
3 changes: 2 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,5 @@ aiodns = "*"
eventlet = "*"
linked-services = {extras = ["django", "aiohttp", "requests"], version = "*"}
celery-task-manager = {extras = ["django"], version = "*"}
django-sql-explorer = {extras = ["xls"], version = "==4.0.2"}
django-sql-explorer = {extras = ["xls"], version = "*"}
contextlib2 = "*"
1,780 changes: 893 additions & 887 deletions Pipfile.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions breathecode/admissions/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1894,6 +1894,7 @@ def post(self, request, cohort_id=None):
resource_belongs_to_user).exclude(excludes).first()

if not resource:
resource_available_now = Q(plan_expires_at__gte=timezone.now())
resource = PlanFinancing.objects.filter(resource_available_now,
resource_belongs_to_user).exclude(excludes).first()

Expand Down
7 changes: 0 additions & 7 deletions breathecode/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""

# keeps this above
import newrelic.agent

newrelic.agent.initialize()

# the rest of your ASGI file contents go here
import os

Expand All @@ -20,5 +15,3 @@
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'breathecode.settings')

application = get_asgi_application()
if os.getenv('NOWRAP_APP') != '1':
application = newrelic.agent.ASGIApplicationWrapper(application)
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
Loading

0 comments on commit 42246c9

Please sign in to comment.