Skip to content

Commit

Permalink
Merge branch 'assessment-notify'
Browse files Browse the repository at this point in the history
  • Loading branch information
stevebrownlee committed Feb 19, 2024
2 parents 5ad161b + f5a7d45 commit ddcc411
Show file tree
Hide file tree
Showing 14 changed files with 609 additions and 424 deletions.
503 changes: 262 additions & 241 deletions LearningAPI.session.sql

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions LearningAPI/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,13 @@ class LearningWeightAdmin(admin.ModelAdmin):
class LearningRecordAdmin(admin.ModelAdmin):
"""Learning records"""
list_display = ('student', 'weight',)
search_fields = ["student__user__last_name"]

@admin.register(LearningRecordEntry)
class LearningRecordEntryAdmin(admin.ModelAdmin):
"""Learning record entries"""
list_display = ('record', 'instructor', 'note',)
search_fields = ["record__student__user__last_name"]

@admin.register(NssUser)
class NssUserAdmin(admin.ModelAdmin):
Expand Down
17 changes: 17 additions & 0 deletions LearningAPI/migrations/0040_alter_capstonetimeline_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.8 on 2024-02-17 15:19

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("LearningAPI", "0039_add_project_stats_view"),
]

operations = [
migrations.AlterField(
model_name="capstonetimeline",
name="date",
field=models.DateTimeField(auto_now=True),
),
]
145 changes: 145 additions & 0 deletions LearningAPI/migrations/0041_students_by_cohort_db_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Generated by Django 4.2.8 on 2024-01-10 18:42

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("LearningAPI", "0040_alter_capstonetimeline_date"),
]

operations = [
migrations.RunSQL(
"""
CREATE FUNCTION get_cohort_student_data(selected_cohort_id INT)
RETURNS TABLE (
user_id INT,
github_handle TEXT,
extra_data TEXT,
student_name TEXT,
current_cohort TEXT,
current_cohort_id INT,
assessment_status_id INT,
current_project_id INT,
current_project_index INT,
current_project_name TEXT,
current_book_id INT,
current_book_index INT,
current_book_name TEXT,
score INT,
student_notes TEXT,
capstone_proposals TEXT,
project_duration DOUBLE PRECISION
) AS $$
BEGIN
RETURN QUERY
SELECT
nu.user_id::int,
nu.github_handle::text,
social.extra_data::text,
au."first_name" || ' ' || au."last_name" AS student_name,
c.name::text AS current_cohort,
c.id::int AS current_cohort_id,
COALESCE(sa.status_id::int, 0) AS assessment_status_id,
sp.project_id::int AS current_project_id,
p.index::int AS current_project_index,
p.name::text AS current_project_name,
b.id::int AS current_book_id,
b.index::int AS current_book_index,
b.name::text AS current_book_name,
COALESCE(lr.total_score, 0)::int AS score,
COALESCE(
json_agg(
json_build_object(
'note_id', sn.id,
'note', sn.note,
'created_on', sn.created_on
)
)
)::text AS student_notes,
COALESCE(
(
SELECT json_agg(
json_build_object(
'id', c."id",
'status', ps.status,
'proposal_url', c."proposal_url",
'created_on', tl.date,
'course_name', cr.name
)
)
FROM "LearningAPI_capstone" c
LEFT JOIN (
SELECT DISTINCT ON (
ct.capstone_id, c.course_id
) *
FROM "LearningAPI_capstonetimeline" ct
JOIN "LearningAPI_capstone" c ON c.id = ct.capstone_id
ORDER BY
c.course_id,
ct.capstone_id,
date desc
) tl ON tl.capstone_id = c.id
LEFT JOIN "LearningAPI_proposalstatus" ps ON ps."id" = tl.status_id
LEFT JOIN "LearningAPI_course" cr ON c.course_id = cr.id
WHERE c."student_id" = nu."user_id"
), '[]'
)::text AS capstone_proposals,
EXTRACT(YEAR FROM AGE(NOW(), sp.date_created)) * 365 +
EXTRACT(MONTH FROM AGE(NOW(), sp.date_created)) * 30 +
EXTRACT(DAY FROM AGE(NOW(), sp.date_created))::double precision AS project_duration
FROM "LearningAPI_nssuser" nu
JOIN "auth_user" au ON au."id" = nu."user_id"
LEFT JOIN "LearningAPI_nssusercohort" nc ON nc."nss_user_id" = nu."id"
LEFT JOIN "LearningAPI_cohort" c ON c."id" = nc."cohort_id"
JOIN "LearningAPI_studentnote" sn ON sn."student_id" = nu."id"
LEFT JOIN "socialaccount_socialaccount" social ON social.user_id = nu.id
LEFT JOIN "LearningAPI_capstone" sc ON sc.student_id = nu."id"
LEFT JOIN "LearningAPI_studentproject" sp
ON sp."student_id" = nu."id"
AND sp.id = (
SELECT id
FROM "LearningAPI_studentproject"
WHERE "student_id" = nu."id"
ORDER BY id DESC
LIMIT 1
)
LEFT JOIN "LearningAPI_project" p ON p."id" = sp."project_id"
LEFT JOIN "LearningAPI_book" b ON b."id" = p."book_id"
LEFT JOIN "LearningAPI_assessment" la
ON b.id = la.book_id
LEFT JOIN "LearningAPI_studentassessment" sa
ON sa."student_id" = nu."id"
AND sa."date_created" = (
SELECT MAX("date_created")
FROM "LearningAPI_studentassessment"
WHERE "student_id" = nu."id"
)
AND sa.assessment_id = la.id
LEFT JOIN (
SELECT lr."student_id", SUM(lw."weight") AS total_score
FROM "LearningAPI_learningrecord" lr
JOIN "LearningAPI_learningrecordentry" lre ON lre."record_id" = lr."id"
JOIN "LearningAPI_learningweight" lw ON lw."id" = lr."weight_id"
WHERE lr."achieved" = true
GROUP BY lr."student_id"
) lr ON lr."student_id" = nu."id"
WHERE nc."cohort_id" = selected_cohort_id
AND au.is_active = TRUE
AND au.is_staff = FALSE
GROUP BY nu.user_id, nu.github_handle, social.extra_data,
student_name, current_cohort, current_cohort_id, assessment_status_id,
current_project_id, current_project_index, current_project_name,
project_duration, current_book_id, current_book_index, current_book_name,
score
ORDER BY b.index ASC,
p.index ASC;
END;
$$ LANGUAGE plpgsql;
""",
"DROP FUNCTION IF EXISTS get_cohort_student_data(INT);"
),
]
2 changes: 1 addition & 1 deletion LearningAPI/models/coursework/capstone_timeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class CapstoneTimeline(models.Model):
"""Model for recording history of capstone proposal"""
capstone = models.ForeignKey("Capstone", on_delete=models.DO_NOTHING, related_name="statuses")
status = models.ForeignKey("ProposalStatus", on_delete=models.DO_NOTHING)
date = models.DateField(auto_now=True, auto_now_add=False)
date = models.DateTimeField(auto_now=True, auto_now_add=False)

@property
def student(self):
Expand Down
68 changes: 0 additions & 68 deletions LearningAPI/models/people/nssuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,74 +29,6 @@ def __str__(self) -> str:
def full_name(self):
return f'{self.user.first_name} {self.user.last_name}'

@property
def book(self):
student_project = StudentProject.objects.filter(student=self).last()
assigned_cohort = self.assigned_cohorts.order_by("-id").last()

if student_project is None:
cohort_course = CohortCourse.objects.get(cohort=assigned_cohort.cohort, index=0)
project = Project.objects.get(
book__course=cohort_course.course, book__index=0, index=0)

return {
"id": project.book.id,
"name": project.book.name,
"project": project.name,
"index": project.book.index,
"project_duration": 0
}

current_project_datestamp = student_project.date_created
project_duration = (datetime.datetime.now().date() - current_project_datestamp).days

return {
"id": student_project.project.book.id,
"name": student_project.project.book.name,
"index": student_project.project.book.index,
"project": student_project.project.name,
"project_duration": project_duration
}

@property
def name(self):
return str(self)

@property
def assessment_status(self):
try:
student_assessment = self.assessments.last() # pylint: disable=E1101
if student_assessment.assessment.book.id != self.book["id"]:
return 0

status = student_assessment.status.status
if status == "In Progress":
return 1
if status == "Ready for Review":
return 2
if status == "Reviewed and Incomplete":
return 3
if status == "Reviewed and Complete":
return 4

except Exception as ex:
return 0

@property
def proposals(self):
try:
lastest_status = CapstoneTimeline.objects.filter(capstone=OuterRef("pk")).order_by("-pk")

proposals = self.capstones.annotate(
course_name=F("course__name"),
current_status_id=Subquery(lastest_status.values('status__id')[:1]),
current_status=Subquery(lastest_status.values('status__status')[:1])
).values('id', 'current_status', 'course_name', 'proposal_url', 'current_status_id')

return proposals
except Exception:
return []

@property
def score(self):
"""Return total learning score"""
Expand Down
1 change: 1 addition & 0 deletions LearningAPI/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .student_view import StudentViewSet
from .auth import register_user
from .auth import login_user
from .notify import notify
from .course_view import CourseViewSet
from .book_view import BookViewSet
from .project_view import ProjectViewSet
Expand Down
2 changes: 1 addition & 1 deletion LearningAPI/views/capstone_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def create(self, request):
try:
Capstone.objects.get(student=student, course=course)

return Response({'message': 'You have already submittted a proposal for this course. If you made updates, just let your instructional team know via Slack'}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': 'You have already submitted a proposal for this course. If you made updates, just let your instructional team know via Slack'}, status=status.HTTP_400_BAD_REQUEST)
except Capstone.DoesNotExist:
proposal = Capstone()
proposal.course = course
Expand Down
11 changes: 10 additions & 1 deletion LearningAPI/views/course_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,16 @@ def stats(self, request, pk):
from django.db import connection

with connection.cursor() as cursor:
cursor.execute("SELECT * FROM get_project_average_start_delay(%s)", [pk])
cursor.execute("""
SELECT
BookName,
BookIndex,
ProjectName,
ProjectIndex,
AverageStartDelay
FROM
get_project_average_start_delay(%s)
""", [pk])
columns = [col[0] for col in cursor.description]
results = [
dict(zip(columns, row))
Expand Down
47 changes: 47 additions & 0 deletions LearningAPI/views/notify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import os
import requests
from rest_framework.decorators import api_view
from rest_framework.response import Response
from LearningAPI.models.people import NssUser

@api_view(['POST'])
def notify(request):
"""
Sends a notification message to a Slack channel.
Args:
request (HttpRequest): The HTTP request object.
Returns:
Response: The HTTP response object with a success message.
Raises:
NssUser.DoesNotExist: If the NssUser object does not exist.
AttributeError: If the user attribute is not present in the request's auth object.
IndexError: If the assigned_cohorts queryset is empty.
AttributeError: If the cohort attribute is not present in the first assigned_cohorts object.
AttributeError: If the slack_channel attribute is not present in the cohort object.
requests.exceptions.Timeout: If the request to the Slack API times out.
"""

student = NssUser.objects.get(user=request.auth.user)
slack_channel = student.assigned_cohorts.order_by("-id").first().cohort.slack_channel

message = request.data.get("message")

headers = {
"Content-Type": "application/x-www-form-urlencoded"
}

requests.post(
"https://slack.com/api/chat.postMessage",
data={
"text": message,
"token": os.getenv("SLACK_BOT_TOKEN"),
"channel": slack_channel
},
headers=headers,
timeout=10
)

return Response({ 'message': 'Notification sent to Slack!'}, status=200)
25 changes: 19 additions & 6 deletions LearningAPI/views/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from rest_framework.response import Response
from allauth.socialaccount.models import SocialAccount
from LearningAPI.models.people import Cohort, NssUserCohort, NssUser
from LearningAPI.models.coursework import StudentProject
from LearningAPI.models.people.student_personality import StudentPersonality
from LearningAPI.views.student_view import StudentNoteSerializer


class Profile(ViewSet):
Expand Down Expand Up @@ -105,9 +105,8 @@ class Meta:

class ProfileSerializer(serializers.ModelSerializer):
"""JSON serializer"""
feedback = StudentNoteSerializer(many=True)
personality = PersonalitySerializer(many=False)
name = serializers.SerializerMethodField()
project = serializers.SerializerMethodField()
email = serializers.SerializerMethodField()
github = serializers.SerializerMethodField()
repos = serializers.SerializerMethodField()
Expand All @@ -117,6 +116,21 @@ class ProfileSerializer(serializers.ModelSerializer):
def get_staff(self, obj):
return obj.user.is_staff

def get_project(self, obj):
project = StudentProject.objects.filter(student=obj).last()
if project is not None:
return {
"id": project.project.id,
"name": project.project.name,
"book_name": project.project.book.name,
}
else:
return {
"id": 0,
"name": "Unassigned"
}


def get_github(self, obj):
github = obj.user.socialaccount_set.get(user=obj.user)
return github.extra_data["login"]
Expand Down Expand Up @@ -144,6 +158,5 @@ def get_capstones(self, obj):

class Meta:
model = NssUser
fields = ('id', 'name', 'email', 'github', 'staff', 'slack_handle',
'current_cohort', 'feedback', 'repos', 'personality',
'assessment_overview', 'capstones',)
fields = ('id', 'name', 'project', 'email', 'github', 'staff', 'slack_handle',
'current_cohort', 'repos', 'assessment_overview', 'capstones',)
Loading

0 comments on commit ddcc411

Please sign in to comment.