Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions frontends/api/src/generated/v0/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2145,6 +2145,18 @@ export interface ProgramCertificate {
* @memberof ProgramCertificate
*/
record_hash: string
/**
*
* @type {string}
* @memberof ProgramCertificate
*/
program_letter_generate_url: string
/**
*
* @type {string}
* @memberof ProgramCertificate
*/
program_letter_share_url: string
/**
*
* @type {string}
Expand Down
12 changes: 12 additions & 0 deletions frontends/api/src/generated/v1/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4834,6 +4834,18 @@ export interface ProgramCertificate {
* @memberof ProgramCertificate
*/
record_hash: string
/**
*
* @type {string}
* @memberof ProgramCertificate
*/
program_letter_generate_url: string
/**
*
* @type {string}
* @memberof ProgramCertificate
*/
program_letter_share_url: string
/**
*
* @type {string}
Expand Down
8 changes: 8 additions & 0 deletions openapi/specs/v0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2249,6 +2249,12 @@ components:
record_hash:
type: string
readOnly: true
program_letter_generate_url:
type: string
readOnly: true
program_letter_share_url:
type: string
readOnly: true
program_title:
type: string
maxLength: 256
Expand Down Expand Up @@ -2322,6 +2328,8 @@ components:
format: date-time
nullable: true
required:
- program_letter_generate_url
- program_letter_share_url
- program_title
- record_hash
- user_email
Expand Down
8 changes: 8 additions & 0 deletions openapi/specs/v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11123,6 +11123,12 @@ components:
record_hash:
type: string
readOnly: true
program_letter_generate_url:
type: string
readOnly: true
program_letter_share_url:
type: string
readOnly: true
program_title:
type: string
maxLength: 256
Expand Down Expand Up @@ -11196,6 +11202,8 @@ components:
format: date-time
nullable: true
required:
- program_letter_generate_url
- program_letter_share_url
- program_title
- record_hash
- user_email
Expand Down
26 changes: 26 additions & 0 deletions profiles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import ulid
from django.contrib.auth import get_user_model
from django.db import transaction
from django.urls import reverse
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
Expand Down Expand Up @@ -368,6 +369,31 @@ class ProgramCertificateSerializer(serializers.ModelSerializer):
Serializer for Program Certificates
"""

program_letter_generate_url = serializers.SerializerMethodField()
program_letter_share_url = serializers.SerializerMethodField()

def get_program_letter_generate_url(self, instance) -> str:
request = self.context.get("request")
letter_url = reverse(
"profile:program-letter-intercept",
kwargs={"program_id": instance.micromasters_program_id},
)
if request:
return request.build_absolute_uri(letter_url)
return letter_url

def get_program_letter_share_url(self, instance) -> str:
request = self.context.get("request")

user = User.objects.get(email=instance.user_email)
letter, created = ProgramLetter.objects.get_or_create(
user=user, certificate=instance
)
letter_url = letter.get_absolute_url()
if request:
return request.build_absolute_uri(letter_url)
return letter_url

class Meta:
model = ProgramCertificate
fields = "__all__"
Expand Down
6 changes: 6 additions & 0 deletions profiles/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from profiles.views import (
CurrentUserRetrieveViewSet,
ProfileViewSet,
ProgramLetterInterceptView,
ProgramLetterViewSet,
UserProgramCertificateViewSet,
UserViewSet,
Expand Down Expand Up @@ -52,4 +53,9 @@
name_initials_avatar_view,
name="name-initials-avatar",
),
re_path(
r"^program_letter/(?P<program_id>[0-9]+)/",
ProgramLetterInterceptView.as_view(),
name="program-letter-intercept",
),
]
27 changes: 25 additions & 2 deletions profiles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

from cairosvg import svg2png # pylint:disable=no-name-in-module
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import HttpResponse
from django.shortcuts import redirect
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.cache import cache_page
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import mixins, viewsets
Expand Down Expand Up @@ -139,3 +142,23 @@ def name_initials_avatar_view(
return redirect(DEFAULT_PROFILE_IMAGE)
svg = generate_svg_avatar(user.profile.name, int(size), color, bgcolor)
return HttpResponse(svg2png(bytestring=svg), content_type="image/png")


@method_decorator(login_required, name="dispatch")
class ProgramLetterInterceptView(View):
"""
View that generates a uuid (via ProgramLetter instance)
and then passes the user along to the shareable letter view
"""

def get(self, request, **kwargs):
program_id = kwargs.get("program_id")
certificate = get_object_or_404(
ProgramCertificate,
user_email=request.user.email,
micromasters_program_id=program_id,
)
letter, created = ProgramLetter.objects.get_or_create(
user=request.user, certificate=certificate
)
return HttpResponseRedirect(letter.get_absolute_url())
46 changes: 45 additions & 1 deletion profiles/views_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
from learning_resources.factories import LearningResourceTopicFactory
from learning_resources.serializers import LearningResourceTopicSerializer
from learning_resources_search.serializers_test import get_request_object
from main.utils import frontend_absolute_url
from profiles.factories import ProgramCertificateFactory, ProgramLetterFactory
from profiles.models import Profile
from profiles.models import Profile, ProgramLetter
from profiles.serializers import (
ProfileSerializer,
ProgramCertificateSerializer,
Expand Down Expand Up @@ -392,6 +393,49 @@ def test_get_user_by_me(mocker, client, user, is_anonymous):
}


@pytest.mark.parametrize("is_anonymous", [True, False])
def test_letter_intercept_view_generates_program_letter(
mocker, client, user, is_anonymous, settings
):
"""
Test that the letter intercept view generates a
ProgramLetter and then passes the user along to the display.
Also test that anonymous users do not generate letters and cant access this page
"""
settings.DATABASE_ROUTERS = []
micromasters_program_id = 1
if not is_anonymous:
client.force_login(user)
cert = ProgramCertificateFactory(
user_email=user.email, micromasters_program_id=micromasters_program_id
)
assert ProgramLetter.objects.filter(user=user).count() == 0

response = client.get(
reverse(
"profile:program-letter-intercept",
kwargs={"program_id": micromasters_program_id},
)
)
assert ProgramLetter.objects.filter(user=user).count() == 1
letter_id = ProgramLetter.objects.get(user=user, certificate=cert).id
assert response.url == frontend_absolute_url(
f"/program_letter/{letter_id}/view"
)
else:
cert = ProgramCertificateFactory(
user_email=user.email, micromasters_program_id=micromasters_program_id
)
ProgramLetterFactory(user=user, certificate=cert)
response = client.get(
reverse(
"profile:program-letter-intercept",
kwargs={"program_id": micromasters_program_id},
)
)
assert response.status_code == 302


@pytest.mark.parametrize("is_anonymous", [True, False])
def test_program_letter_api_view(mocker, client, rf, user, is_anonymous, settings): # noqa: PLR0913
"""
Expand Down
Loading