Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/pennlabs/platform into sh…
Browse files Browse the repository at this point in the history
…ared-actions-migration
  • Loading branch information
joyliu-q committed Oct 13, 2023
2 parents 89e67d1 + 203e11c commit 7dfea0b
Show file tree
Hide file tree
Showing 13 changed files with 1,569 additions and 762 deletions.
1 change: 0 additions & 1 deletion backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
codecov = "*"
black = "==21.9b0"
unittest-xml-reporting = "*"
flake8 = "*"
Expand Down
1,903 changes: 1,155 additions & 748 deletions backend/Pipfile.lock

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion backend/accounts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@
from django.shortcuts import redirect
from django.urls import reverse

from accounts.models import Email, Major, PhoneNumber, School, Student, User
from accounts.models import (
Email,
Major,
PhoneNumber,
PrivacyResource,
PrivacySetting,
School,
Student,
User,
)


class EmailAdmin(admin.ModelAdmin):
Expand Down Expand Up @@ -77,6 +86,8 @@ class SchoolAdmin(admin.ModelAdmin):
admin.site.register(Email, EmailAdmin)
admin.site.register(Major, MajorAdmin)
admin.site.register(School, SchoolAdmin)
admin.site.register(PrivacySetting)
admin.site.register(PrivacyResource)


class LabsAdminSite(admin.AdminSite):
Expand Down
61 changes: 61 additions & 0 deletions backend/accounts/migrations/0005_privacyresource_privacysetting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Generated by Django 3.2.9 on 2023-03-04 21:59

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("accounts", "0004_user_profile_pic"),
]

operations = [
migrations.CreateModel(
name="PrivacyResource",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name="PrivacySetting",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("enabled", models.BooleanField(default=True)),
(
"resource",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="resource",
to="accounts.privacyresource",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="privacy_setting",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
52 changes: 52 additions & 0 deletions backend/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,55 @@ class PhoneNumber(models.Model):

def __str__(self):
return f"{self.user} - {self.value}"


class PrivacyResource(models.Model):
"""
Represents a resource utilized by Penn Labs that users reserve
the right to withhold.
"""

name = models.CharField(max_length=255)


@receiver(post_save, sender=PrivacyResource)
def add_privacy_resource(sender, instance, created, **kwargs):
"""
This post_save hook triggers whenever a new privacy resource is added.
Each User will receive a new privacy setting with this resource, enabled
to true.
"""
users = User.objects.all()
settings = [
PrivacySetting(user=user, resource=instance, enabled=True) for user in users
]
# Bulk creating for all User objects
PrivacySetting.objects.bulk_create(settings, ignore_conflicts=True)


class PrivacySetting(models.Model):
user = models.ForeignKey(
get_user_model(), related_name="privacy_setting", on_delete=models.CASCADE
)
resource = models.ForeignKey(
PrivacyResource, related_name="resource", on_delete=models.CASCADE
)
enabled = models.BooleanField(default=True)


@receiver(post_save, sender=User)
def load_privacy_settings(sender, instance, created, **kwargs):
"""
This post_save hook triggers automatically when a User object is saved, and loads in default
privacy settings for the User
"""

# In most cases, first checking if settings exists should reduce the number of queries
# to the database
if not instance.privacy_setting.exists():
resources = PrivacyResource.objects.all()
settings = [
PrivacySetting(user=instance, resource=resource, enabled=True)
for resource in resources
]
PrivacySetting.objects.bulk_create(settings, ignore_conflicts=True)
58 changes: 57 additions & 1 deletion backend/accounts/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
from rest_framework import serializers

from accounts.mixins import ManyToManySaveMixin
from accounts.models import Email, Major, PhoneNumber, School, Student, User
from accounts.models import (
Email,
Major,
PhoneNumber,
PrivacyResource,
PrivacySetting,
School,
Student,
User,
)
from accounts.verification import sendEmailVerification, sendSMSVerification


Expand Down Expand Up @@ -220,3 +229,50 @@ def update(self, instance, validated_data):
serializer.is_valid(raise_exception=True)
serializer.save()
return instance


class FindUserSerializer(serializers.ModelSerializer):
# SerializerMethodFields are read_only
first_name = serializers.CharField(source="get_preferred_name", required=False)
groups = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name")
student = StudentSerializer()
profile_pic = serializers.ImageField(required=False, allow_empty_file=True)

class Meta:
model = User
fields = (
"uuid",
"pennid",
"first_name",
"last_name",
"username",
"groups",
"student",
"profile_pic",
)

read_only_fields = (
"uuid",
"pennid",
"first_name",
"last_name",
"username",
"groups",
"student",
"profile_pic",
)


class PrivacyResourceSerializer(serializers.ModelSerializer):
class Meta:
model = PrivacyResource
fields = ("name",)


class PrivacySettingSerializer(serializers.ModelSerializer):
resource = PrivacyResourceSerializer()

class Meta:
model = PrivacySetting
fields = ("id", "resource", "enabled")
read_only_fields = ("id", "resource")
5 changes: 5 additions & 0 deletions backend/accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
DevLoginView,
DevLogoutView,
EmailViewSet,
FindUserView,
LoginView,
LogoutView,
MajorViewSet,
PhoneNumberViewSet,
PrivacySettingView,
ProductAdminView,
ProfilePicViewSet,
SchoolViewSet,
Expand Down Expand Up @@ -42,6 +44,9 @@
path("token/", TokenView.as_view(), name="token"),
path("introspect/", UUIDIntrospectTokenView.as_view(), name="introspect"),
path("productadmin/", ProductAdminView.as_view(), name="productadmin"),
path("privacy/", PrivacySettingView.as_view(), name="privacy"),
path("privacy/<int:pk>/", PrivacySettingView.as_view(), name="privacy"),
path("user/<str:username>", FindUserView.as_view(), name="user"),
]

urlpatterns += router.urls
Expand Down
39 changes: 37 additions & 2 deletions backend/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.uploadedfile import UploadedFile
from django.db.models import Case, IntegerField, Q, Value, When
from django.http import HttpResponseServerError
from django.http import Http404, HttpResponseServerError
from django.http.response import HttpResponse, HttpResponseBadRequest
from django.shortcuts import redirect, render
from django.utils import timezone
Expand All @@ -20,7 +20,7 @@
from oauth2_provider.models import get_access_token_model
from oauth2_provider.views import IntrospectTokenView
from oauth2_provider.views.mixins import ProtectedResourceMixin
from rest_framework import generics, status, viewsets
from rest_framework import generics, mixins, status, viewsets
from rest_framework.decorators import action
from rest_framework.filters import SearchFilter
from rest_framework.permissions import IsAuthenticated
Expand All @@ -31,8 +31,10 @@
from accounts.models import Major, School, User
from accounts.serializers import (
EmailSerializer,
FindUserSerializer,
MajorSerializer,
PhoneNumberSerializer,
PrivacySettingSerializer,
SchoolSerializer,
UserSearchSerializer,
UserSerializer,
Expand Down Expand Up @@ -286,6 +288,23 @@ def get_object(self):
return self.request.user


class FindUserView(generics.RetrieveAPIView):
"""
get:
Return information about the user associated with the provided username.
"""

serializer_class = FindUserSerializer
permission_classes = [IsAuthenticated]

def get_object(self):
try:
user = User.objects.get(username=self.kwargs["username"])
except ObjectDoesNotExist:
raise Http404
return user


class ProfilePicViewSet(viewsets.ViewSet):
"""
post:
Expand Down Expand Up @@ -527,3 +546,19 @@ def post(self, request, format=None):
)
user.user_permissions.add(permission)
return Response({"detail": "success"})


class PrivacySettingView(
generics.GenericAPIView, mixins.ListModelMixin, mixins.UpdateModelMixin
):
serializer_class = PrivacySettingSerializer
permission_classes = [IsAuthenticated]

def get_queryset(self):
return self.request.user.privacy_setting.select_related("resource").all()

def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
47 changes: 46 additions & 1 deletion backend/tests/accounts/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@
from django.utils import timezone
from rest_framework import serializers

from accounts.models import Email, Major, PhoneNumber, School, User
from accounts.models import (
Email,
Major,
PhoneNumber,
PrivacyResource,
PrivacySetting,
School,
User,
)
from accounts.serializers import (
EmailSerializer,
MajorSerializer,
PhoneNumberSerializer,
PrivacyResourceSerializer,
PrivacySettingSerializer,
SchoolSerializer,
StudentSerializer,
UserSearchSerializer,
Expand Down Expand Up @@ -546,3 +556,38 @@ def test_verification_timeout(self):
with self.assertRaises(serializers.ValidationError):
serializer.save()
self.assertFalse(email.verified)


class PrivacyResourceSerializerTestCase(TestCase):
def setUp(self):
self.resource = PrivacyResource.objects.create(name="ACADEMIC_IDENTITY")
self.serializer = PrivacyResourceSerializer(self.resource)

def test_privacy_resource(self):
sample_response = {"name": self.resource.name}
self.assertEqual(self.serializer.data, sample_response)


class PrivacySettingSerializerTestCase(TestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(
pennid=1,
username="student",
password="secret",
first_name="First",
last_name="Last",
email="[email protected]",
)
self.resource = PrivacyResource.objects.create(name="ACADEMIC_IDENTITY")
self.setting = PrivacySetting.objects.create(
user=self.user, resource=self.resource, enabled=True
)
self.serializer = PrivacySettingSerializer(self.setting)

def test_privacy_resource(self):
sample_response = {
"id": self.setting.id,
"resource": PrivacyResourceSerializer(self.resource).data,
"enabled": self.setting.enabled,
}
self.assertEqual(self.serializer.data, sample_response)
Loading

0 comments on commit 7dfea0b

Please sign in to comment.