Skip to content

Commit

Permalink
Merge branch 'master' of github.com:pennlabs/penn-mobile into fix/lau…
Browse files Browse the repository at this point in the history
…ndry-api-rewrite
  • Loading branch information
dr-Jess committed Nov 17, 2024
2 parents 0c88430 + 44965e9 commit 0663073
Show file tree
Hide file tree
Showing 35 changed files with 1,360 additions and 579 deletions.
9 changes: 4 additions & 5 deletions .github/workflows/build-and-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,12 @@ jobs:
cache-to: type=local,dest=/tmp/.buildx-cache
tags: pennlabs/penn-mobile-backend:latest,pennlabs/penn-mobile-backend:${{ github.sha }}
outputs: type=docker,dest=/tmp/image.tar
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
with:
name: build-backend
path: /tmp/image.tar
needs: backend-check


build-frontend:
name: Build frontend
runs-on: ubuntu-latest
Expand All @@ -74,7 +73,7 @@ jobs:
cache-to: type=local,dest=/tmp/.buildx-cache
tags: pennlabs/penn-mobile-frontend:latest,pennlabs/penn-mobile-frontend:${{ github.sha }}
outputs: type=docker,dest=/tmp/image.tar
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
with:
name: build-frontend
path: /tmp/image.tar
Expand All @@ -86,8 +85,8 @@ jobs:
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
- uses: geekyeggo/delete-artifact@v1
- uses: actions/download-artifact@v4
- uses: geekyeggo/delete-artifact@v5
with:
name: |-
build-backend
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ Final Steps:
- `pipenv run python manage.py migrate`
- `pipenv run python manage.py runserver 8000`

Setting up precommit:
Making git blame [correct](https://github.com/pennlabs/penn-mobile/pull/287) (_optional_):
- `git config blame.ignoreRevsFile .git-blame-ignore-revs`

Setting up precommit (_optional_):
- `pipenv run pre-commit install`

## Creating Users
Expand All @@ -47,4 +50,4 @@ In separate terminal windows, run the following commands:

## Exploring the API

- Expore the API via our [auto-generated documentation](https://pennmobile.org/api/documentation/)! This is a really good way to click around and discover stuff.
- Expore the API via our [auto-generated documentation](https://pennmobile.org/api/documentation/)! This is a really good way to click around and discover stuff.
25 changes: 24 additions & 1 deletion backend/gsr_booking/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@
from gsr_booking.models import GSR, Group, GroupMembership, GSRBooking, Reservation


class GroupMembershipInline(admin.TabularInline):
model = GroupMembership
extra = 0

readonly_fields = ["name"]

def name(self, obj):
return obj.user.get_full_name()

def get_fields(self, request, obj=None):
fields = super().get_fields(request, obj)
to_remove = ["user", "name"]
return ["name"] + [f for f in fields if f not in to_remove]


class GroupAdmin(admin.ModelAdmin):
search_fields = ["name__icontains"]
list_display = ["name"]
ordering = ["name"]

inlines = [GroupMembershipInline]


class GroupMembershipAdmin(admin.ModelAdmin):
search_fields = ["user__username__icontains", "group__name__icontains"]

Expand All @@ -16,7 +39,7 @@ def get_queryset(self, request):
ordering = ["-in_use"]


admin.site.register(Group)
admin.site.register(Group, GroupAdmin)
admin.site.register(GroupMembership, GroupMembershipAdmin)
admin.site.register(GSR, GSRAdmin)
admin.site.register(GSRBooking)
Expand Down
31 changes: 0 additions & 31 deletions backend/gsr_booking/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,13 @@ def get_is_wharton(self, obj):
return obj["lid"] == 1


class MiniUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["username", "first_name", "last_name"]


class GroupMembershipSerializer(serializers.ModelSerializer):
user = MiniUserSerializer(read_only=True)
group = serializers.SlugRelatedField(slug_field="name", queryset=Group.objects.all())
color = serializers.SlugRelatedField(slug_field="color", read_only=True, source="group")

class Meta:
model = GroupMembership
fields = [
"user",
"group",
"type",
"pennkey_allow",
Expand Down Expand Up @@ -73,29 +65,6 @@ def to_internal_value(self, data):
return None # TODO: If you want to update based on BookingField, implement this.


class UserSerializer(serializers.ModelSerializer):
booking_groups = serializers.SerializerMethodField()

def get_booking_groups(self, obj):
result = []
for membership in GroupMembership.objects.filter(accepted=True, user=obj):
result.append(
{
"name": membership.group.name,
"id": membership.group.id,
"color": membership.group.color,
"pennkey_allow": membership.pennkey_allow,
"notifications": membership.notifications,
}
)

return result

class Meta:
model = User
fields = ["username", "booking_groups"]


class GSRSerializer(serializers.ModelSerializer):
class Meta:
model = GSR
Expand Down
4 changes: 2 additions & 2 deletions backend/gsr_booking/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
GroupMembershipViewSet,
GroupViewSet,
Locations,
MyMembershipViewSet,
RecentGSRs,
ReservationsView,
UserViewSet,
)
from utils.cache import Cache


router = routers.DefaultRouter()

router.register(r"users", UserViewSet)
router.register(r"mymemberships", MyMembershipViewSet, "mymemberships")
router.register(r"membership", GroupMembershipViewSet)
router.register(r"groups", GroupViewSet)

Expand Down
53 changes: 9 additions & 44 deletions backend/gsr_booking/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,66 +11,31 @@

from gsr_booking.api_wrapper import APIError, GSRBooker, WhartonGSRBooker
from gsr_booking.models import GSR, Group, GroupMembership, GSRBooking
from gsr_booking.serializers import (
GroupMembershipSerializer,
GroupSerializer,
GSRSerializer,
UserSerializer,
)
from gsr_booking.serializers import GroupMembershipSerializer, GroupSerializer, GSRSerializer
from pennmobile.analytics import Metric, record_analytics


User = get_user_model()


class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
Can specify `me` instead of the `username` to retrieve details on the current user.
"""

queryset = User.objects.all().prefetch_related(
Prefetch("booking_groups", Group.objects.filter(memberships__accepted=True))
)
class MyMembershipViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = [IsAuthenticated]
serializer_class = UserSerializer
lookup_field = "username"
filter_backends = [DjangoFilterBackend]
filterset_fields = ["username", "first_name", "last_name"]

def get_object(self):
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
param = self.kwargs[lookup_url_kwarg]
if param == "me":
return self.request.user
else:
return super().get_object()
serializer_class = GroupMembershipSerializer

def get_queryset(self):
if not self.request.user.is_authenticated:
return User.objects.none()
return GroupMembership.objects.filter(user=self.request.user, accepted=True)

queryset = User.objects.all()
queryset = queryset.prefetch_related(
Prefetch(
"memberships",
GroupMembership.objects.filter(
group__in=self.request.user.booking_groups.all(), accepted=True
),
)
)
return queryset

@action(detail=True, methods=["get"])
def invites(self, request, username=None):
@action(detail=False, methods=["get"])
def invites(self, request):
"""
Retrieve all invites for a given user.
"""

user = get_object_or_404(User, username=username)
return Response(
GroupMembershipSerializer(
GroupMembership.objects.filter(
user=user, accepted=False, group__in=self.request.user.booking_groups.all()
user=request.user,
accepted=False,
group__in=self.request.user.booking_groups.all(),
),
many=True,
).data
Expand Down
8 changes: 8 additions & 0 deletions backend/pennmobile/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,11 @@
AWS_QUERYSTRING_AUTH = False
AWS_S3_FILE_OVERWRITE = False
AWS_DEFAULT_ACL = "public-read"

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = os.environ.get("SMTP_HOST", "")
EMAIL_USE_TLS = True
EMAIL_PORT = os.environ.get("SMTP_PORT", 587)
EMAIL_HOST_USER = os.environ.get("SMTP_USERNAME", "")
EMAIL_HOST_PASSWORD = os.environ.get("SMTP_PASSWORD", "")
DEFAULT_FROM_EMAIL = os.environ.get("SMTP_FROM_EMAIL", EMAIL_HOST_USER)
20 changes: 20 additions & 0 deletions backend/pennmobile/templates/email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Notification</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; margin: 0; padding: 0;">

<div>
<p>{{ message|linebreaksbr }}</p>

<hr style="border: 0; border-top: 1px solid #ddd; margin: 30px 0 20px 0;">

<em style="font-size: 12px; color: #666;">Please do not reply to this email. Replies to this email address are not monitored.</em>

</div>

</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def get_years(self, years):
# creates new class year in August in preparation for upcoming school year
if years is None:
return (
[+x for x in range(4)]
[timezone.localtime().year + x for x in range(4)]
if timezone.localtime().month < 8
else [timezone.localtime().year + x for x in range(1, 5)]
)
Expand Down
36 changes: 36 additions & 0 deletions backend/portal/migrations/0016_poll_creator_post_creator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 4.2.9 on 2024-04-17 04:44

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


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("portal", "0015_auto_20240226_2236"),
]

operations = [
migrations.AddField(
model_name="poll",
name="creator",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
),
),
migrations.AddField(
model_name="post",
name="creator",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
),
),
]
43 changes: 41 additions & 2 deletions backend/portal/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from django.db.models import Q
from django.utils import timezone

from utils.email import get_backend_manager_emails, send_automated_email


User = get_user_model()

Expand Down Expand Up @@ -48,17 +50,54 @@ class Content(models.Model):
admin_comment = models.CharField(max_length=255, null=True, blank=True)
target_populations = models.ManyToManyField(TargetPopulation, blank=True)
priority = models.IntegerField(default=0)
creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)

class Meta:
abstract = True

def _get_email_subject(self):
return f"[Portal] {self.__class__._meta.model_name.capitalize()} #{self.id}"

def _on_create(self):
send_automated_email.delay_on_commit(
self._get_email_subject(),
get_backend_manager_emails(),
(
f"A new {self.__class__._meta.model_name} for {self.club_code} "
f"has been created by {self.creator}."
),
)

def _on_status_change(self):
if email := getattr(self.creator, "email", None):
send_automated_email.delay_on_commit(
self._get_email_subject(),
[email],
f"Your {self.__class__._meta.model_name} status for {self.club_code} has been "
+ f"changed to {self.status}."
+ (
f"\n\nAdmin comment: {self.admin_comment}"
if self.admin_comment and self.status == self.STATUS_REVISION
else ""
),
)

def save(self, *args, **kwargs):
prev = self.__class__.objects.filter(id=self.id).first()
super().save(*args, **kwargs)
if prev is None:
self._on_create()
return
if self.status != prev.status:
self._on_status_change()


class Poll(Content):
question = models.CharField(max_length=255)
multiselect = models.BooleanField(default=False)

def __str__(self):
return f"{self.id} - {self.club_code} - {self.question}"
return self.question


class PollOption(models.Model):
Expand All @@ -85,4 +124,4 @@ class Post(Content):
image = models.ImageField(upload_to="portal/images", null=True, blank=True)

def __str__(self):
return f"{self.id} - {self.club_code} - {self.title}"
return self.title
Loading

0 comments on commit 0663073

Please sign in to comment.