Skip to content

Commit

Permalink
Merge branch 'main' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
tommygonzaleza authored Oct 31, 2024
2 parents 5e3bc5f + ac4a6f0 commit 5173aa5
Show file tree
Hide file tree
Showing 19 changed files with 353 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .gitpod.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ RUN sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release
wget --quiet -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - && \
echo "deb https://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-18 main" | sudo tee /etc/apt/sources.list.d/llvm.list && \
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - && \
sudo install-packages postgresql-16 postgresql-contrib-16 redis-server netcat passwd
sudo install-packages postgresql-16 postgresql-contrib-16 redis-server

# Setup PostgreSQL server for user gitpod
ENV PATH="/usr/lib/postgresql/16/bin:$PATH"
Expand Down
47 changes: 27 additions & 20 deletions breathecode/admissions/admin.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
import os
import pytz
import base64
import json
import os
import re
from random import choice

import pytz
import requests
import base64
from django.contrib import admin
from django import forms
from django.contrib import admin, messages
from django.contrib.auth.admin import UserAdmin
from django.db.models import Q, QuerySet
from django.utils import timezone
from django.utils.html import format_html
from django.contrib.auth.admin import UserAdmin
from django.contrib import messages
from breathecode.utils.datetime_integer import from_now
from breathecode.utils import getLogger
from django.db.models import QuerySet
from breathecode.activity.tasks import get_attendancy_log

from breathecode.activity.tasks import get_attendancy_log
from breathecode.assignments.actions import sync_student_tasks
from breathecode.marketing.tasks import add_cohort_slug_as_acp_tag, add_cohort_task_to_student
from breathecode.utils import getLogger
from breathecode.utils.datetime_integer import from_now

from .actions import ImportCohortTimeSlots, test_syllabus
from .models import (
Academy,
SyllabusSchedule,
City,
Cohort,
CohortTimeSlot,
CohortUser,
Country,
City,
SyllabusVersion,
UserAdmissions,
Syllabus,
CohortTimeSlot,
SyllabusSchedule,
SyllabusScheduleTimeSlot,
SyllabusVersion,
UserAdmissions,
)
from .actions import ImportCohortTimeSlots, test_syllabus
from .tasks import async_test_syllabus
from breathecode.assignments.actions import sync_student_tasks
from random import choice
from django.db.models import Q

logger = getLogger(__name__)

Expand Down Expand Up @@ -382,7 +382,7 @@ def async_test_syllabus_integrity(modeladmin, request, queryset):

@admin.register(SyllabusVersion)
class SyllabusVersionAdmin(admin.ModelAdmin):
list_display = ("version", "syllabus", "integrity", "owner")
list_display = ("version", "syllabus", "integrity", "owner", "url_path")
search_fields = ["syllabus__name", "syllabus__slug"]
list_filter = ["syllabus__private", "syllabus__academy_owner"]
actions = [
Expand Down Expand Up @@ -413,6 +413,13 @@ def integrity(self, obj):
</table>"""
)

def url_path(self, obj):
return format_html(
f"""
<a rel='noopener noreferrer' target='_blank' href='/v1/admissions/syllabus/{obj.syllabus.id}/version/{obj.version}/preview'>preview</a>
"""
)


class CohortTimeSlotForm(forms.ModelForm):

Expand Down
55 changes: 55 additions & 0 deletions breathecode/admissions/templates/syllabus.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ slug }}.py</title>
<base target="_blank" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/themes/prism.min.css" integrity="sha512-tN7Ec6zAFaVSG3TpNAKtk4DOHNpSwKHxxrsiw4GHKESGPs5njn/0sMCUMl2svV4wo4BK/rCP7juYz+zx+l6oeQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<style>
body{
margin: 40px 0 0 0;
padding: 0;
font-family: "Helvetica Neue",Helvetica;
}
.topbar{
position: fixed;
display: flex;
width: 100%;
top: 0;
padding: 10px;
background: black;
color: white;
}
.topbar a, .topbar a:visited{
color: white;
text-decoration: none;
}
.container{
padding: 50px;
}
.menu{
margin: 0;
align: right;
text-align: right;
width: 200px;
}
.menu li{
list-style: none;
display: inline-block;
}
</style>
<div class="topbar">
<span class="title">{{ name }} v{{ version }} </span>
<ul class="menu">
<li><a href="#">Github</a></li>
</ul>
</div>
<pre class="container"><code class="language-{{ language }}">{{ code }}</code></pre>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/prism.min.js" integrity="sha512-/Swpp6aCQ0smuZ+zpklJqMClcUlvxhpLf9aAcM7JjJrj2waCU4dikm3biOtMVAflOOeniW9qzaNXNrbOAOWFCw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.27.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha512-GP4x8UWxWyh4BMbyJGOGneiTbkrWEF5izsVJByzVLodP8CuJH/n936+yQDMJJrOPUHLgyPbLiGw2rXmdvGdXHA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</body>
</html>
12 changes: 12 additions & 0 deletions breathecode/admissions/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
PublicCohortView,
SyllabusAssetView,
SyllabusScheduleView,
SyllabusVersionCSVView,
SyllabusVersionView,
SyllabusView,
UserMeView,
Expand All @@ -31,6 +32,7 @@
get_single_academy,
get_timezones,
handle_test_syllabus,
render_syllabus_preview,
)

app_name = "admissions"
Expand Down Expand Up @@ -108,6 +110,16 @@
path("syllabus/<int:syllabus_id>", SyllabusView.as_view(), name="syllabus_id"),
path("syllabus/<int:syllabus_id>/version", SyllabusVersionView.as_view(), name="syllabus_id_version"),
path("syllabus/version", AllSyllabusVersionsView.as_view(), name="syllabus_version"),
path(
"syllabus/<str:syllabus_id>/version/<str:version>.csv",
SyllabusVersionCSVView.as_view(),
name="syllabus_id_version_csv",
),
path(
"syllabus/<str:syllabus_id>/version/<str:version>/preview",
render_syllabus_preview,
name="syllabus_id_version_preview",
),
path(
"syllabus/<int:syllabus_id>/version/<int:version>",
SyllabusVersionView.as_view(),
Expand Down
135 changes: 135 additions & 0 deletions breathecode/admissions/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import csv
import json
import logging
import math

import pytz
from adrf.decorators import api_view
from capyc.core.i18n import translation
from capyc.rest_framework.exceptions import ValidationException
from django.contrib.auth.models import AnonymousUser, User
from django.db.models import FloatField, Max, Q, Value
from django.http import HttpResponse
from django.shortcuts import render
from django.utils import timezone
from rest_framework import status
from rest_framework.decorators import permission_classes
Expand All @@ -28,6 +33,7 @@
localize_query,
)
from breathecode.utils.find_by_full_name import query_like_by_full_name
from breathecode.utils.views import render_message

from .actions import find_asset_on_json, test_syllabus, update_asset_on_json
from .models import (
Expand Down Expand Up @@ -78,6 +84,54 @@
logger = logging.getLogger(__name__)


@api_view(["GET"])
@permission_classes([AllowAny])
def render_syllabus_preview(request, syllabus_id, version):

syllabus_slug = None
if not syllabus_id.isnumeric():
syllabus_slug = syllabus_id
syllabus_id = None

syllabus_version = None
if version == "latest":
syllabus_version = (
SyllabusVersion.objects.filter(
Q(syllabus__id=syllabus_id) | Q(syllabus__slug=syllabus_slug),
)
.latest("created_at")
.first()
)
else:
syllabus_version = (
SyllabusVersion.objects.filter(
Q(syllabus__id=syllabus_id) | Q(syllabus__slug=syllabus_slug),
)
.filter(version=version)
.first()
)

if syllabus_version is None:
return render_message(request, f"Syllabus Version {syllabus_id} {version} not found")

payload = GetSyllabusVersionSerializer(syllabus_version).data
response = render(
request,
"syllabus.html",
{
**payload,
"code": json.dumps(payload["json"], indent=4),
"theme": request.GET.get("theme", "light"),
"plain": request.GET.get("plain", "false"),
},
)

# Set Content-Security-Policy header
response["Content-Security-Policy"] = "frame-ancestors 'self' https://4geeks.com"

return response


@api_view(["GET"])
@permission_classes([AllowAny])
def get_timezones(request, id=None):
Expand Down Expand Up @@ -1725,6 +1779,87 @@ def put(self, request, syllabus_id=None, syllabus_slug=None, version=None, acade
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class SyllabusVersionCSVView(APIView):

@capable_of("read_syllabus")
def get(self, request, syllabus_id, version, academy_id):
lang = get_user_language(request)
if "class_days_per_week" not in request.GET:
raise ValidationException(
translation(
lang,
en="Missing class_days_per_week in query parameters",
es="Falta class_days_per_week en los parámetros de consulta",
slug="missing-class-days-per-week",
)
)

class_days_per_week = int(request.GET.get("class_days_per_week"))

syllabus_slug = None
if not syllabus_id.isnumeric():
syllabus_slug = syllabus_id
syllabus_id = None

syllabus_version = None
if version == "latest":
syllabus_version = SyllabusVersion.objects.filter(
Q(syllabus__id=syllabus_id) | Q(syllabus__slug=syllabus_slug),
Q(syllabus__academy_owner__id=academy_id) | Q(syllabus__private=False),
).latest("created_at")

if syllabus_version is None and version is not None and version != "latest":
syllabus_version = SyllabusVersion.objects.filter(
Q(syllabus__id=syllabus_id) | Q(syllabus__slug=syllabus_slug),
Q(syllabus__academy_owner__id=academy_id) | Q(syllabus__private=False),
version=version,
).first()

# Create an HTTP response object and set the content type to CSV
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="syllabus_{syllabus_slug}.csv"'

# Create a CSV writer object
writer = csv.writer(response)

# Write the header row based on language
if lang == "es":
writer.writerow(["Semana", "Días", "Temas a impartir", "Descripción", "Recursos", "Objetivos"])
else:
writer.writerow(["Week", "Day", "Topics", "Description", "Resources", "Objectives"])

# Initialize cumulative day counter
cumulative_days = 1

# Write the data rows for each day
for day in sorted(syllabus_version.json["days"], key=lambda x: x["position"]):
week_number = math.ceil(cumulative_days / class_days_per_week)
if lang == "es":
writer.writerow(
[
f"Semana {week_number}",
f"Día {day['id']}: {day['label']}",
", ".join([lesson["title"] for lesson in day["lessons"]]),
day.get("description", ""),
", ".join([tech["title"] if isinstance(tech, dict) else tech for tech in day["technologies"]]),
day.get("teacher_instructions", ""),
]
)
else:
writer.writerow(
[
f"Week {week_number}",
f"Day {day['id']}: {day['label']}",
", ".join([lesson["title"] for lesson in day["lessons"]]),
day.get("description", ""),
", ".join([tech["title"] if isinstance(tech, dict) else tech for tech in day["technologies"]]),
day.get("teacher_instructions", ""),
]
)
cumulative_days += day["duration_in_days"]
return response


class AllSyllabusVersionsView(APIView):
"""List all snippets, or create a new snippet."""

Expand Down
2 changes: 1 addition & 1 deletion breathecode/assessment/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def change_status_answered(modeladmin, request, queryset):

@admin.register(UserAssessment)
class UserAssessmentAdmin(admin.ModelAdmin):
search_fields = ["title", "question__assessment__title"]
search_fields = ["title", "assessment__title"]
readonly_fields = ("token",)
list_display = ["id", "title", "current_status", "lang", "owner", "total_score", "assessment", "academy"]
list_filter = ["lang", "status", "academy"]
Expand Down
26 changes: 26 additions & 0 deletions breathecode/assessment/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,32 @@ class GetUserAssessmentSerializer(serpy.Serializer):
created_at = serpy.Field()


class HookUserAssessmentSerializer(serpy.Serializer):
id = serpy.Field()
token = serpy.Field()
title = serpy.Field()
lang = serpy.Field()

academy = AcademySmallSerializer(required=False)
assessment = AssessmentSmallSerializer()

owner = UserSerializer(required=False)
owner_email = serpy.Field()
owner_phone = serpy.Field()

status = serpy.Field()
status_text = serpy.Field()

conversion_info = serpy.Field()
total_score = serpy.Field()
comment = serpy.Field()

started_at = serpy.Field()
finished_at = serpy.Field()

created_at = serpy.Field()


class PublicUserAssessmentSerializer(serpy.Serializer):
id = serpy.Field()
token = serpy.Field()
Expand Down
Loading

0 comments on commit 5173aa5

Please sign in to comment.