Skip to content

Commit

Permalink
✨(backend) add shared note retrieve api endpoint
Browse files Browse the repository at this point in the history
retrieve shared notes by recreating access link
  • Loading branch information
alfredpichard committed Apr 25, 2023
1 parent a3c6817 commit d9496c8
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).

## [4.0.0-beta.20] - 2023-04-20

### Added

- Add a widget to download the available shared notes on the classroom dashboard

### Fixed

- downgrade python social auth to version 4.3.0 (#2197)
Expand Down
1 change: 1 addition & 0 deletions env.d/development.dist
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ [email protected]
# BBB server credentials
DJANGO_BBB_ENABLED=False
DJANGO_BBB_API_ENDPOINT=https://example.com/bbb/api
DJANGO_BBB_SHARED_NOTES_RETRIEVE_LINK=https://example.com/bbb
DJANGO_BBB_API_SECRET=BbbSecret
# BBB callback through scalelite may use a different secret to sign the sent token
DJANGO_BBB_API_CALLBACK_SECRET=BbbOtherSecret
Expand Down
11 changes: 10 additions & 1 deletion src/backend/marsha/bbb/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from marsha.core.admin import link_field

from .models import Classroom, ClassroomDocument
from .models import Classroom, ClassroomDocument, ClassroomSharedNote


class ClassroomDocumentInline(admin.TabularInline):
Expand Down Expand Up @@ -93,3 +93,12 @@ class ClassroomDocumentAdmin(admin.ModelAdmin):
"classroom__playlist__organization__name",
"filename",
)


@admin.register(ClassroomSharedNote)
class ClassroomSharedNoteAdmin(admin.ModelAdmin):
"""Admin class for the ClassroomSharedNote model"""

verbose_name = _("Classroom shared note")
list_display = ("id", link_field("classroom"), "updated_on")
readonly_fields = ["id", "updated_on"]
2 changes: 2 additions & 0 deletions src/backend/marsha/bbb/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
create,
end,
get_recordings,
get_session_shared_note,
join,
process_recordings,
)
Expand Down Expand Up @@ -365,6 +366,7 @@ def service_end(self, request, *args, **kwargs):
Type[rest_framework.response.Response]
HttpResponse with the serialized classroom.
"""
get_session_shared_note(classroom=self.get_object())
try:
response = end(classroom=self.get_object())
status = 200
Expand Down
83 changes: 83 additions & 0 deletions src/backend/marsha/bbb/migrations/0014_classroomsharednote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Generated by Django 4.1.7 on 2023-04-20 15:49

import uuid

from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):
dependencies = [
("bbb", "0013_classroom_tools_parameters"),
]

operations = [
migrations.CreateModel(
name="ClassroomSharedNote",
fields=[
(
"deleted",
models.DateTimeField(db_index=True, editable=False, null=True),
),
(
"deleted_by_cascade",
models.BooleanField(default=False, editable=False),
),
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
help_text="primary key for the shared note as UUID",
primary_key=True,
serialize=False,
verbose_name="id",
),
),
(
"created_on",
models.DateTimeField(
default=django.utils.timezone.now,
editable=False,
help_text="date and time at which a shared note was created",
verbose_name="created on",
),
),
(
"updated_on",
models.DateTimeField(
auto_now=True,
help_text="date and time at which a shared note was last updated",
verbose_name="updated on",
),
),
(
"shared_note_url",
models.CharField(
blank=True,
help_text="url of the classroom shared note",
max_length=255,
null=True,
verbose_name="shared note url",
),
),
(
"classroom",
models.ForeignKey(
help_text="classroom to which this shared note belongs",
on_delete=django.db.models.deletion.PROTECT,
related_name="shared notes",
to="bbb.classroom",
verbose_name="classroom shared note",
),
),
],
options={
"verbose_name": "Classroom shared note",
"verbose_name_plural": "Classroom shared notes",
"db_table": "classroom_shared_note",
"ordering": ["-updated_on"],
},
),
]
29 changes: 29 additions & 0 deletions src/backend/marsha/bbb/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,32 @@ class Meta:
ordering = ["-created_on"]
verbose_name = _("Classroom recording")
verbose_name_plural = _("Classroom recordings")


class ClassroomSharedNote(BaseModel):
"""Model representing a shared note in a classroom."""

classroom = models.ForeignKey(
to=Classroom,
related_name="shared_notes",
verbose_name=_("classroom shared notes"),
help_text=_("classroom to which this shared note belongs"),
# don't allow hard deleting a classroom if it still contains a recording
on_delete=models.PROTECT,
)

shared_note_url = models.CharField(
max_length=255,
verbose_name=_("shared note url"),
help_text=_("url of the classroom shared note"),
null=True,
blank=True,
)

class Meta:
"""Options for the ``ClassroomSharedNote`` model."""

db_table = "classroom_shared_note"
ordering = ["-updated_on"]
verbose_name = _("Classroom shared note")
verbose_name_plural = _("Classroom shared notes")
44 changes: 43 additions & 1 deletion src/backend/marsha/bbb/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@

from rest_framework import serializers

from marsha.bbb.models import Classroom, ClassroomDocument, ClassroomRecording
from marsha.bbb.models import (
Classroom,
ClassroomDocument,
ClassroomRecording,
ClassroomSharedNote,
)
from marsha.bbb.utils.bbb_utils import (
ApiMeetingException,
get_meeting_infos,
Expand Down Expand Up @@ -54,6 +59,30 @@ class Meta: # noqa
)


class ClassroomSharedNoteSerializer(ReadOnlyModelSerializer):
"""A serializer to display a ClassroomRecording resource."""

class Meta: # noqa
model = ClassroomSharedNote
fields = (
"id",
"classroom",
"shared_note_url",
"updated_on",
)
read_only_fields = (
"id",
"classroom",
"shared_note_url",
"updated_on",
)

# Make sure classroom UUID is converted to a string during serialization
classroom = serializers.PrimaryKeyRelatedField(
read_only=True, pk_field=serializers.CharField()
)


class ClassroomSerializer(serializers.ModelSerializer):
"""A serializer to display a Classroom resource."""

Expand All @@ -72,6 +101,7 @@ class Meta: # noqa
"starting_at",
"estimated_duration",
"recordings",
"shared_notes",
# specific generated fields
"infos",
"invite_token",
Expand Down Expand Up @@ -100,6 +130,7 @@ class Meta: # noqa
invite_token = serializers.SerializerMethodField()
instructor_token = serializers.SerializerMethodField()
recordings = serializers.SerializerMethodField()
shared_notes = serializers.SerializerMethodField()

def get_infos(self, obj):
"""Meeting infos from BBB server."""
Expand Down Expand Up @@ -137,6 +168,17 @@ def get_recordings(self, obj):
).data
return []

def get_shared_notes(self, obj):
"""Get the shared notes for the classroom.
Only available for admins.
"""
if self.context.get("is_admin", True):
return ClassroomSharedNoteSerializer(
obj.shared_notes.all(), many=True, context=self.context
).data
return []

def update(self, instance, validated_data):
if any(
attribute in validated_data
Expand Down
106 changes: 106 additions & 0 deletions src/backend/marsha/bbb/tests/bbb_utils/test_get_session_shared_note.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Tests for the get_recordings service in the ``bbb`` app of the Marsha project."""
from django.test import TestCase, override_settings

import responses

from marsha.bbb.factories import ClassroomFactory, ClassroomRecordingFactory
from marsha.bbb.utils.bbb_utils import get_session_shared_note
from marsha.core.tests.testing_utils import reload_urlconf


@override_settings(
BBB_SHARED_NOTES_RETRIEVE_LINK="https://10.7.7.1/bigbluebutton/sharednotes"
)
@override_settings(BBB_API_ENDPOINT="https://10.7.7.1/bigbluebutton/api")
@override_settings(BBB_API_SECRET="SuperSecret")
@override_settings(BBB_ENABLED=True)
class ClassroomServiceTestCase(TestCase):
"""Test our intentions about the Classroom get_recordings service."""

maxDiff = None

@classmethod
def setUpClass(cls):
super().setUpClass()

# Force URLs reload to use BBB_ENABLED
reload_urlconf()

@responses.activate
def test_get_shared_notes(self):
"""Validate response when multiple recordings exists."""
classroom = ClassroomFactory(
meeting_id="881e8986-9673-11ed-a1eb-0242ac120002", started=True
)

responses.add(
responses.GET,
"https://10.7.7.1/bigbluebutton/api/getMeetingInfo",
match=[
responses.matchers.query_param_matcher(
{
"meetingID": "7a567d67-29d3-4547-96f3-035733a4dfaa",
"checksum": "7f13332ec54e7df0a02d07904746cb5b8b330498",
}
)
],
body="""
<response>
<returncode>SUCCESS</returncode>
<meetingName>random-6256545</meetingName>
<meetingID>random-6256545</meetingID>
<internalMeetingID>ab0da0b4a1f283e94cfefdf32dd761eebd5461ce-1635514947533</internalMeetingID>
<createTime>1635514947533</createTime>
<createDate>Fri Oct 29 13:42:27 UTC 2021</createDate>
<voiceBridge>77581</voiceBridge>
<dialNumber>613-555-1234</dialNumber>
<attendeePW>trac</attendeePW>
<moderatorPW>trusti</moderatorPW>
<running>true</running>
<duration>0</duration>
<hasUserJoined>true</hasUserJoined>
<recording>false</recording>
<hasBeenForciblyEnded>false</hasBeenForciblyEnded>
<startTime>1635514947596</startTime>
<endTime>0</endTime>
<participantCount>1</participantCount>
<listenerCount>0</listenerCount>
<voiceParticipantCount>0</voiceParticipantCount>
<videoCount>0</videoCount>
<maxUsers>0</maxUsers>
<moderatorCount>0</moderatorCount>
<attendees>
<attendee>
<userID>w_2xox6leao03w</userID>
<fullName>User 1907834</fullName>
<role>MODERATOR</role>
<isPresenter>true</isPresenter>
<isListeningOnly>false</isListeningOnly>
<hasJoinedVoice>false</hasJoinedVoice>
<hasVideo>false</hasVideo>
<clientType>HTML5</clientType>
</attendee>
<attendee>
<userID>w_bau7cr7aefju</userID>
<fullName>User 1907834</fullName>
<role>VIEWER</role>
<isPresenter>false</isPresenter>
<isListeningOnly>false</isListeningOnly>
<hasJoinedVoice>false</hasJoinedVoice>
<hasVideo>false</hasVideo>
<clientType>HTML5</clientType>
</attendee>
</attendees>
<metadata>
</metadata>
<isBreakout>false</isBreakout>
</response>
""",
status=200,
)

shared_note_object = get_session_shared_note(classroom.meeting_id)
assert (
shared_note_object.shared_note_url
== "https://10.7.7.1/bigbluebutton/sharednotes/ab0da0b4a1f283e94cfefdf32dd761eebd5461ce-1635514947533/notes.html"
)
Loading

0 comments on commit d9496c8

Please sign in to comment.