From 074fda5c8a82380682f57c726db0c796fda7c663 Mon Sep 17 00:00:00 2001
From: cphalen
Date: Mon, 13 Dec 2021 01:53:32 -0500
Subject: [PATCH 01/64] Ticket model
---
backend/clubs/models.py | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/backend/clubs/models.py b/backend/clubs/models.py
index 64877ee8b..ad10f12e0 100644
--- a/backend/clubs/models.py
+++ b/backend/clubs/models.py
@@ -1695,6 +1695,17 @@ class QuestionResponse(models.Model):
response = models.TextField(blank=True)
+class Ticket(models.Model):
+ """
+ Represents a single ticket for a particular event
+ """
+
+ event = models.ForeignKey(Event, on_delete=models.DO_NOTHING)
+ buyer = models.ForeignKey(
+ get_user_model(), on_delete=models.SET_NULL, null=True, related_name="tickets"
+ )
+
+
@receiver(models.signals.pre_delete, sender=Asset)
def asset_delete_cleanup(sender, instance, **kwargs):
if instance.file:
From 4171a610dcb762b8776c0a6a7e6f6aaa549dfb92 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sun, 16 Jan 2022 14:00:16 -0500
Subject: [PATCH 02/64] some basic untested functionality
---
backend/clubs/models.py | 16 +++++--
backend/clubs/serializers.py | 18 +++++++
backend/clubs/views.py | 92 ++++++++++++++++++++++++++++++++++++
3 files changed, 122 insertions(+), 4 deletions(-)
diff --git a/backend/clubs/models.py b/backend/clubs/models.py
index ad10f12e0..d16b9655c 100644
--- a/backend/clubs/models.py
+++ b/backend/clubs/models.py
@@ -941,6 +941,10 @@ def create_thumbnail(self, request=None):
def __str__(self):
return self.name
+ @property
+ def tickets_count(self):
+ return Ticket.objects.count(event=self)
+
class Favorite(models.Model):
"""
@@ -1697,12 +1701,16 @@ class QuestionResponse(models.Model):
class Ticket(models.Model):
"""
- Represents a single ticket for a particular event
+ Represents an instance of a ticket for an event
"""
- event = models.ForeignKey(Event, on_delete=models.DO_NOTHING)
- buyer = models.ForeignKey(
- get_user_model(), on_delete=models.SET_NULL, null=True, related_name="tickets"
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+ event = models.ForeignKey(
+ Event, related_name="tickets", on_delete=models.DO_NOTHING
+ )
+ type = models.CharField(max_length=100)
+ owner = models.ForeignKey(
+ get_user_model(), related_name="tickets", on_delete=models.SET_NULL, blank=True
)
diff --git a/backend/clubs/serializers.py b/backend/clubs/serializers.py
index 898fa3633..e2f7efafe 100644
--- a/backend/clubs/serializers.py
+++ b/backend/clubs/serializers.py
@@ -54,6 +54,7 @@
TargetStudentType,
TargetYear,
Testimonial,
+ Ticket,
Year,
)
from clubs.utils import clean
@@ -1721,6 +1722,23 @@ class Meta:
fields = ("club", "role", "title", "active", "public")
+class TicketSerializer(serializers.ModelSerializer):
+
+ """
+ Used to return a ticket object
+ """
+
+ owner = serializers.SerializerMethodField("get_owner_name")
+ event = EventSerializer(source="obj.event")
+
+ def get_owner_name(self, obj):
+ return obj.owner.get_full_name()
+
+ class Meta:
+ model = Ticket
+ fields = ("id", "event", "owner")
+
+
class UserUUIDSerializer(serializers.ModelSerializer):
"""
Used to get the uuid of a user (for ICS Calendar export)
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 2bf170b26..d95939f0f 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -91,6 +91,7 @@
Subscribe,
Tag,
Testimonial,
+ Ticket,
Year,
ZoomMeetingVisit,
get_mail_type_annotation,
@@ -157,6 +158,7 @@
SubscribeSerializer,
TagSerializer,
TestimonialSerializer,
+ TicketSerializer,
UserClubVisitSerializer,
UserClubVisitWriteSerializer,
UserMembershipInviteSerializer,
@@ -4206,6 +4208,96 @@ def get_object(self):
return user
+class TicketViewSet(viewsets.ModelViewSet):
+ """
+ create:
+ Create tickets for an event
+
+ buy:
+ Buy one ticket
+ """
+
+ permission_classes = [IsAuthenticated]
+ serializer_class = TicketSerializer
+ http_method_names = ["get", "post"]
+
+ def create(self, request, *args, **kwargs):
+ """
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ event:
+ type: integer
+ quantities:
+ type: array
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ count:
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ detail:
+ type: string
+ description: A success or error message.
+ """
+ name = request.data.get("name")
+ event = request.data.get("event")
+ quantities = request.data.get("quantities")
+
+ for type, count in quantities.items():
+ for _ in range(count):
+ Ticket.objects.create(name=name, event=event, type=type)
+
+ @action(detail=False, methods=["get"])
+ def buy(self, request, *args, **kwargs):
+ """
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ event:
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/Ticket"
+ ---
+ """
+
+ # Get first unsold ticket of type
+ ticket = Ticket.objects.filter(
+ event=request.data.get("event"),
+ type=request.data.get("type"),
+ user__isnull=True,
+ ).first()
+ ticket.owner = request.user
+ ticket.save()
+ return Response(TicketSerializer(ticket).data)
+
+ def get_queryset(self):
+ return Ticket.objects.filter(user=self.request.user)
+
+
class MemberInviteViewSet(viewsets.ModelViewSet):
"""
update:
From ef872be68d7924e66d4774faf9db85c2063b8814 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sun, 16 Jan 2022 17:17:26 -0500
Subject: [PATCH 03/64] tested routes, added view ticket count route
---
backend/clubs/admin.py | 2 +
backend/clubs/migrations/0091_ticket.py | 26 +++++++
backend/clubs/models.py | 6 +-
backend/clubs/serializers.py | 4 +-
backend/clubs/urls.py | 2 +
backend/clubs/views.py | 99 +++++++++++++++++++++----
6 files changed, 122 insertions(+), 17 deletions(-)
create mode 100644 backend/clubs/migrations/0091_ticket.py
diff --git a/backend/clubs/admin.py b/backend/clubs/admin.py
index f99652bf3..3abbef4a8 100644
--- a/backend/clubs/admin.py
+++ b/backend/clubs/admin.py
@@ -48,6 +48,7 @@
TargetStudentType,
TargetYear,
Testimonial,
+ Ticket,
Year,
ZoomMeetingVisit,
)
@@ -443,3 +444,4 @@ class ZoomMeetingVisitAdmin(admin.ModelAdmin):
admin.site.register(Year, YearAdmin)
admin.site.register(ZoomMeetingVisit, ZoomMeetingVisitAdmin)
admin.site.register(AdminNote)
+admin.site.register(Ticket)
\ No newline at end of file
diff --git a/backend/clubs/migrations/0091_ticket.py b/backend/clubs/migrations/0091_ticket.py
new file mode 100644
index 000000000..db569b6fb
--- /dev/null
+++ b/backend/clubs/migrations/0091_ticket.py
@@ -0,0 +1,26 @@
+# Generated by Django 3.2.8 on 2022-01-16 20:59
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('clubs', '0090_adminnote'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Ticket',
+ fields=[
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+ ('type', models.CharField(max_length=100)),
+ ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='tickets', to='clubs.event')),
+ ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/backend/clubs/models.py b/backend/clubs/models.py
index d16b9655c..41773763e 100644
--- a/backend/clubs/models.py
+++ b/backend/clubs/models.py
@@ -1710,7 +1710,11 @@ class Ticket(models.Model):
)
type = models.CharField(max_length=100)
owner = models.ForeignKey(
- get_user_model(), related_name="tickets", on_delete=models.SET_NULL, blank=True
+ get_user_model(),
+ related_name="tickets",
+ on_delete=models.SET_NULL,
+ blank=True,
+ null=True,
)
diff --git a/backend/clubs/serializers.py b/backend/clubs/serializers.py
index e2f7efafe..3e6ef79a6 100644
--- a/backend/clubs/serializers.py
+++ b/backend/clubs/serializers.py
@@ -1729,10 +1729,10 @@ class TicketSerializer(serializers.ModelSerializer):
"""
owner = serializers.SerializerMethodField("get_owner_name")
- event = EventSerializer(source="obj.event")
+ event = EventSerializer()
def get_owner_name(self, obj):
- return obj.owner.get_full_name()
+ return obj.owner.get_full_name() if obj.owner else "None"
class Meta:
model = Ticket
diff --git a/backend/clubs/urls.py b/backend/clubs/urls.py
index 6c6fbbc85..1a740ccb2 100644
--- a/backend/clubs/urls.py
+++ b/backend/clubs/urls.py
@@ -42,6 +42,7 @@
SubscribeViewSet,
TagViewSet,
TestimonialViewSet,
+ TicketViewSet,
UserGroupAPIView,
UserPermissionAPIView,
UserUpdateAPIView,
@@ -67,6 +68,7 @@
router.register(r"searches", SearchQueryViewSet, basename="searches")
router.register(r"memberships", MembershipViewSet, basename="members")
router.register(r"requests", MembershipRequestViewSet, basename="requests")
+router.register(r"tickets", TicketViewSet, basename="tickets")
router.register(r"schools", SchoolViewSet, basename="schools")
router.register(r"majors", MajorViewSet, basename="majors")
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index d95939f0f..7be2b041e 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -2218,6 +2218,54 @@ def get_serializer_class(self):
return EventWriteSerializer
return EventSerializer
+ @action(detail=True, methods=["get"])
+ def tickets(self, request, *args, **kwargs):
+ """
+ Get information about tickets for particular event
+ ___
+ requestBody: {}
+ responses:
+ "200":
+ content:
+ application/json
+ schema:
+ type: object
+ properties:
+ totals:
+ type: array
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ count:
+ type: integer
+ available:
+ type: array
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ count:
+ type: integer
+ ---
+
+ """
+
+ tickets = Ticket.objects.filter(event=kwargs["id"])
+ types = tickets.values_list("type", flat=True).distinct()
+ totals = []
+ available = []
+
+ for type in types:
+ totals.append({"type": type, "count": tickets.filter(type=type).count()})
+ available.append({"type": type, "count": (
+ tickets.filter(type=type, owner__isnull=True).count()
+ )})
+
+ return Response({"totals": totals, "available": available})
+
@action(detail=True, methods=["post"])
def upload(self, request, *args, **kwargs):
"""
@@ -4215,6 +4263,9 @@ class TicketViewSet(viewsets.ModelViewSet):
buy:
Buy one ticket
+
+ list:
+ List all tickets owned by user
"""
permission_classes = [IsAuthenticated]
@@ -4253,15 +4304,21 @@ def create(self, request, *args, **kwargs):
type: string
description: A success or error message.
"""
- name = request.data.get("name")
- event = request.data.get("event")
+ event = get_object_or_404(Event, id=request.data.get("event"))
quantities = request.data.get("quantities")
+ membership = find_membership_helper(request.user, event.club)
- for type, count in quantities.items():
- for _ in range(count):
- Ticket.objects.create(name=name, event=event, type=type)
+ if membership.role <= 10: # Create tickets allowed if officer+
+ for item in quantities:
+ for _ in range(item["count"]):
+ Ticket.objects.create(event=event, type=item["type"])
+ return Response([])
+ else:
+ return Response(
+ {"detail": "Unauthorized"}, status=status.HTTP_401_UNAUTHORIZED
+ )
- @action(detail=False, methods=["get"])
+ @action(detail=False, methods=["post"])
def buy(self, request, *args, **kwargs):
"""
requestBody:
@@ -4283,19 +4340,33 @@ def buy(self, request, *args, **kwargs):
- $ref: "#/components/schemas/Ticket"
---
"""
+ type = request.data.get("type")
+ event = request.data.get("event")
+
+ # If ticket already owned, do nothing
+ if Ticket.objects.filter(event=event, owner=request.user.id).first():
+ return Response(
+ {"detail": "Ticket to event already owned by user"},
+ status=status.HTTP_400_BAD_REQUEST,
+ )
- # Get first unsold ticket of type
+ # Otherwise get first unowned ticket of requested type
ticket = Ticket.objects.filter(
- event=request.data.get("event"),
- type=request.data.get("type"),
- user__isnull=True,
+ event=request.data.get("event"), type=type, owner__isnull=True,
).first()
- ticket.owner = request.user
- ticket.save()
- return Response(TicketSerializer(ticket).data)
+
+ if ticket:
+ ticket.owner = request.user
+ ticket.save()
+ return Response(TicketSerializer(ticket).data)
+ else:
+ return Response(
+ {"detail": f"No tickets of type {type} left!"},
+ status=status.HTTP_403_FORBIDDEN,
+ )
def get_queryset(self):
- return Ticket.objects.filter(user=self.request.user)
+ return Ticket.objects.filter(owner=self.request.user.id)
class MemberInviteViewSet(viewsets.ModelViewSet):
From c55fd75bc869473ee9748d8badfd6378df4e26fb Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sun, 16 Jan 2022 17:29:31 -0500
Subject: [PATCH 04/64] lint
---
backend/clubs/admin.py | 2 +-
backend/clubs/migrations/0091_ticket.py | 41 ++++++++++++++++-----
backend/clubs/views.py | 48 +++++++++++++------------
3 files changed, 60 insertions(+), 31 deletions(-)
diff --git a/backend/clubs/admin.py b/backend/clubs/admin.py
index 3abbef4a8..5a0f0acb5 100644
--- a/backend/clubs/admin.py
+++ b/backend/clubs/admin.py
@@ -444,4 +444,4 @@ class ZoomMeetingVisitAdmin(admin.ModelAdmin):
admin.site.register(Year, YearAdmin)
admin.site.register(ZoomMeetingVisit, ZoomMeetingVisitAdmin)
admin.site.register(AdminNote)
-admin.site.register(Ticket)
\ No newline at end of file
+admin.site.register(Ticket)
diff --git a/backend/clubs/migrations/0091_ticket.py b/backend/clubs/migrations/0091_ticket.py
index db569b6fb..a76becfee 100644
--- a/backend/clubs/migrations/0091_ticket.py
+++ b/backend/clubs/migrations/0091_ticket.py
@@ -1,26 +1,51 @@
# Generated by Django 3.2.8 on 2022-01-16 20:59
+import uuid
+
+import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
-import django.db.models.deletion
-import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('clubs', '0090_adminnote'),
+ ("clubs", "0090_adminnote"),
]
operations = [
migrations.CreateModel(
- name='Ticket',
+ name="Ticket",
fields=[
- ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
- ('type', models.CharField(max_length=100)),
- ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='tickets', to='clubs.event')),
- ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets', to=settings.AUTH_USER_MODEL)),
+ (
+ "id",
+ models.UUIDField(
+ default=uuid.uuid4,
+ editable=False,
+ primary_key=True,
+ serialize=False,
+ ),
+ ),
+ ("type", models.CharField(max_length=100)),
+ (
+ "event",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="tickets",
+ to="clubs.event",
+ ),
+ ),
+ (
+ "owner",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="tickets",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
],
),
]
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 7be2b041e..56a30b9c7 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -2222,35 +2222,34 @@ def get_serializer_class(self):
def tickets(self, request, *args, **kwargs):
"""
Get information about tickets for particular event
- ___
+ ---
requestBody: {}
responses:
"200":
content:
- application/json
+ application/json:
schema:
type: object
properties:
totals:
type: array
- items:
- type: object
- properties:
- type:
- type: string
- count:
- type: integer
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ count:
+ type: integer
available:
type: array
- items:
- type: object
- properties:
- type:
- type: string
- count:
- type: integer
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ count:
+ type: integer
---
-
"""
tickets = Ticket.objects.filter(event=kwargs["id"])
@@ -2260,9 +2259,12 @@ def tickets(self, request, *args, **kwargs):
for type in types:
totals.append({"type": type, "count": tickets.filter(type=type).count()})
- available.append({"type": type, "count": (
- tickets.filter(type=type, owner__isnull=True).count()
- )})
+ available.append(
+ {
+ "type": type,
+ "count": (tickets.filter(type=type, owner__isnull=True).count()),
+ }
+ )
return Response({"totals": totals, "available": available})
@@ -4321,6 +4323,8 @@ def create(self, request, *args, **kwargs):
@action(detail=False, methods=["post"])
def buy(self, request, *args, **kwargs):
"""
+ Buy a ticket
+ ---
requestBody:
content:
application/json:
@@ -4336,8 +4340,8 @@ def buy(self, request, *args, **kwargs):
content:
application/json:
schema:
- allOf:
- - $ref: "#/components/schemas/Ticket"
+ allOf:
+ - $ref: "#/components/schemas/Ticket"
---
"""
type = request.data.get("type")
From 2d0db0d4cab7fcbbc2e88ae4331ce5e4b2793115 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sat, 12 Nov 2022 14:57:40 -0500
Subject: [PATCH 05/64] email confirmation, qrcode, retrieve ticket
---
backend/clubs/models.py | 50 +++-
backend/clubs/serializers.py | 12 +-
backend/clubs/views.py | 268 +++++++++++-------
.../templates/emails/ticket_confirmation.html | 49 ++++
4 files changed, 272 insertions(+), 107 deletions(-)
create mode 100644 backend/templates/emails/ticket_confirmation.html
diff --git a/backend/clubs/models.py b/backend/clubs/models.py
index 41773763e..5a8091dae 100644
--- a/backend/clubs/models.py
+++ b/backend/clubs/models.py
@@ -1,11 +1,14 @@
+import base64
import datetime
import os
import re
import uuid
import warnings
+from io import BytesIO
from urllib.parse import urlparse
import pytz
+import qrcode
import requests
import yaml
from django.conf import settings
@@ -1571,7 +1574,9 @@ class ApplicationCommittee(models.Model):
name = models.TextField(blank=True)
application = models.ForeignKey(
- ClubApplication, related_name="committees", on_delete=models.CASCADE,
+ ClubApplication,
+ related_name="committees",
+ on_delete=models.CASCADE,
)
def get_word_limit(self):
@@ -1623,7 +1628,9 @@ class ApplicationMultipleChoice(models.Model):
value = models.TextField(blank=True)
question = models.ForeignKey(
- ApplicationQuestion, related_name="multiple_choice", on_delete=models.CASCADE,
+ ApplicationQuestion,
+ related_name="multiple_choice",
+ on_delete=models.CASCADE,
)
@@ -1717,6 +1724,45 @@ class Ticket(models.Model):
null=True,
)
+ def get_qr(self):
+ """
+ Return a QR code image linking to the ticket page
+ """
+ if not self.owner:
+ return None
+
+ url = f"https://{settings.DOMAIN}/api/tickets/{self.id}"
+ qr_image = qrcode.make(url, box_size=20, border=0)
+ return qr_image
+
+ def send_confirmation_email(self):
+ """
+ Send a confirmation email to the ticket owner after purchase
+ """
+ owner = self.owner
+
+ output = BytesIO()
+ qr_image = self.get_qr()
+ qr_image.save(output, format="PNG")
+ decoded_image = base64.b64encode(output.getvalue()).decode("ascii")
+
+ context = {
+ "first_name": self.owner.first_name,
+ "name": self.event.name,
+ "type": self.type,
+ "start_time": self.event.start_time,
+ "end_time": self.event.end_time,
+ "qr": decoded_image,
+ }
+
+ if self.owner.email:
+ send_mail_helper(
+ name="ticket_confirmation",
+ subject=f"Ticket confirmation for {owner.get_full_name()}",
+ emails=[owner.email],
+ context=context,
+ )
+
@receiver(models.signals.pre_delete, sender=Asset)
def asset_delete_cleanup(sender, instance, **kwargs):
diff --git a/backend/clubs/serializers.py b/backend/clubs/serializers.py
index 3e6ef79a6..69ec913ad 100644
--- a/backend/clubs/serializers.py
+++ b/backend/clubs/serializers.py
@@ -1736,7 +1736,7 @@ def get_owner_name(self, obj):
class Meta:
model = Ticket
- fields = ("id", "event", "owner")
+ fields = ("id", "event", "type", "owner")
class UserUUIDSerializer(serializers.ModelSerializer):
@@ -2007,7 +2007,9 @@ def get_clubs(self, obj):
# hide non public memberships if not superuser
if user is None or not user.has_perm("clubs.manage_club"):
queryset = queryset.filter(
- membership__person=obj, membership__public=True, approved=True,
+ membership__person=obj,
+ membership__public=True,
+ approved=True,
)
serializer = MembershipClubListSerializer(
@@ -2408,7 +2410,8 @@ def save(self):
ApplicationMultipleChoice.objects.filter(question=question_obj).delete()
for choice in multiple_choice:
ApplicationMultipleChoice.objects.create(
- value=choice["value"], question=question_obj,
+ value=choice["value"],
+ question=question_obj,
)
# manually create committee choices as Django does not
@@ -2697,7 +2700,8 @@ def save(self):
for name in committees:
if name not in prev_committee_names:
ApplicationCommittee.objects.create(
- name=name, application=application_obj,
+ name=name,
+ application=application_obj,
)
return application_obj
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 56a30b9c7..9cc11866b 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -1072,7 +1072,8 @@ def get_queryset(self):
self.request.user, get_user_model()
):
SearchQuery(
- person=self.request.user, query=self.request.query_params.get("search"),
+ person=self.request.user,
+ query=self.request.query_params.get("search"),
).save()
# select subset of clubs if requested
@@ -1665,7 +1666,9 @@ def constitutions(self, request, *args, **kwargs):
query = (
Club.objects.filter(badges=badge, archived=False)
.order_by(Lower("name"))
- .prefetch_related(Prefetch("asset_set", to_attr="prefetch_asset_set"),)
+ .prefetch_related(
+ Prefetch("asset_set", to_attr="prefetch_asset_set"),
+ )
)
if request.user.is_authenticated:
query = query.prefetch_related(
@@ -2198,6 +2201,12 @@ class ClubEventViewSet(viewsets.ModelViewSet):
destroy:
Delete an event.
+
+ tickets:
+ Get or create tickets for particular event
+
+ buy:
+ Buy a ticket for an event
"""
permission_classes = [EventPermission | IsSuperuser]
@@ -2218,6 +2227,56 @@ def get_serializer_class(self):
return EventWriteSerializer
return EventSerializer
+ @action(detail=True, methods=["post"])
+ def buy(self, request, *args, **kwargs):
+ """
+ Buy a ticket
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/Ticket"
+ ---
+ """
+ type = request.data.get("type")
+ event = self.get_object()
+
+ # If ticket already owned, do nothing
+ if Ticket.objects.filter(event=event, owner=request.user.id).first():
+ return Response(
+ {"detail": "Ticket to event already owned by user"},
+ status=status.HTTP_400_BAD_REQUEST,
+ )
+
+ # Otherwise get first unowned ticket of requested type
+ ticket = Ticket.objects.filter(
+ event=event,
+ type=type,
+ owner__isnull=True,
+ ).first()
+
+ if ticket:
+ ticket.owner = request.user
+ ticket.save()
+ ticket.send_confirmation_email()
+ return Response(TicketSerializer(ticket).data)
+ else:
+ return Response(
+ {"detail": f"No tickets of type {type} left!"},
+ status=status.HTTP_403_FORBIDDEN,
+ )
+
@action(detail=True, methods=["get"])
def tickets(self, request, *args, **kwargs):
"""
@@ -2231,6 +2290,17 @@ def tickets(self, request, *args, **kwargs):
schema:
type: object
properties:
+ buyers:
+ type: array
+ items:
+ type: object
+ properties:
+ fullname:
+ type: string
+ id:
+ type: string
+ type:
+ type: string
totals:
type: array
items:
@@ -2251,8 +2321,10 @@ def tickets(self, request, *args, **kwargs):
type: integer
---
"""
-
- tickets = Ticket.objects.filter(event=kwargs["id"])
+ event = self.get_object()
+ tickets = Ticket.objects.filter(event=event).annotate(
+ fullname=Concat("owner__first_name", Value(" "), "owner__last_name")
+ )
types = tickets.values_list("type", flat=True).distinct()
totals = []
available = []
@@ -2266,7 +2338,57 @@ def tickets(self, request, *args, **kwargs):
}
)
- return Response({"totals": totals, "available": available})
+ buyers = tickets.filter(owner__isnull=False).values("id", "fullname", "type")
+
+ return Response({"totals": totals, "available": available, "buyers": buyers})
+
+ @tickets.mapping.put
+ def create_tickets(self, request, *args, **kwargs):
+ """
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ quantities:
+ type: array
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ count:
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ detail:
+ type: string
+ description: A success or error message.
+ """
+ event = self.get_object()
+ quantities = request.data.get("quantities")
+ membership = find_membership_helper(request.user, event.club)
+
+ if membership.role <= 10: # Create tickets allowed if officer+
+
+ Ticket.objects.filter(event=event).delete() # Idempotency
+
+ for item in quantities:
+ for _ in range(item["count"]):
+ Ticket.objects.create(event=event, type=item["type"])
+ return Response([])
+ else:
+ return Response(
+ {"detail": "Unauthorized"}, status=status.HTTP_401_UNAUTHORIZED
+ )
@action(detail=True, methods=["post"])
def upload(self, request, *args, **kwargs):
@@ -3008,7 +3130,9 @@ def destroy(self, request, *args, **kwargs):
def get_queryset(self):
return MembershipRequest.objects.filter(
- person=self.request.user, withdrew=False, club__archived=False,
+ person=self.request.user,
+ withdrew=False,
+ club__archived=False,
)
@@ -4132,7 +4256,9 @@ def get(self, request):
try:
response = zoom_api_call(
- request.user, "GET", "https://api.zoom.us/v2/users/{uid}/settings",
+ request.user,
+ "GET",
+ "https://api.zoom.us/v2/users/{uid}/settings",
)
except requests.exceptions.HTTPError as e:
raise DRFValidationError(
@@ -4253,125 +4379,61 @@ def get_operation_id(self, **kwargs):
def get_object(self):
user = self.request.user
prefetch_related_objects(
- [user], "profile__school", "profile__major",
+ [user],
+ "profile__school",
+ "profile__major",
)
return user
class TicketViewSet(viewsets.ModelViewSet):
"""
- create:
- Create tickets for an event
-
- buy:
- Buy one ticket
-
list:
List all tickets owned by user
+
+ retrieve:
+ Retrieve an individual ticket's data
+
+ qr:
+ Get a ticket's QR code
"""
permission_classes = [IsAuthenticated]
serializer_class = TicketSerializer
http_method_names = ["get", "post"]
+ lookup_field = "id"
- def create(self, request, *args, **kwargs):
- """
- requestBody:
- content:
- application/json:
- schema:
- type: object
- properties:
- name:
- type: string
- event:
- type: integer
- quantities:
- type: array
- items:
- type: object
- properties:
- type:
- type: string
- count:
- type: integer
- responses:
- "200":
- content:
- application/json:
- schema:
- type: object
- properties:
- detail:
- type: string
- description: A success or error message.
- """
- event = get_object_or_404(Event, id=request.data.get("event"))
- quantities = request.data.get("quantities")
- membership = find_membership_helper(request.user, event.club)
-
- if membership.role <= 10: # Create tickets allowed if officer+
- for item in quantities:
- for _ in range(item["count"]):
- Ticket.objects.create(event=event, type=item["type"])
- return Response([])
- else:
- return Response(
- {"detail": "Unauthorized"}, status=status.HTTP_401_UNAUTHORIZED
- )
+ def retrieve(self, request, *args, **kwargs):
+ return Response(TicketSerializer(Ticket.objects.get(id=kwargs["id"])).data)
- @action(detail=False, methods=["post"])
- def buy(self, request, *args, **kwargs):
+ @action(detail=True, methods=["get"])
+ def qr(self, request, *args, **kwargs):
"""
- Buy a ticket
+ Return a QR code png image representing a link to the ticket.
---
- requestBody:
- content:
- application/json:
- schema:
- type: object
- properties:
- type:
- type: string
- event:
- type: integer
+ operationId: Generate QR Code for ticket
responses:
"200":
+ description: Return a png image representing a QR code to the ticket.
content:
- application/json:
+ image/png:
schema:
- allOf:
- - $ref: "#/components/schemas/Ticket"
+ type: binary
---
"""
- type = request.data.get("type")
- event = request.data.get("event")
-
- # If ticket already owned, do nothing
- if Ticket.objects.filter(event=event, owner=request.user.id).first():
- return Response(
- {"detail": "Ticket to event already owned by user"},
- status=status.HTTP_400_BAD_REQUEST,
- )
-
- # Otherwise get first unowned ticket of requested type
- ticket = Ticket.objects.filter(
- event=request.data.get("event"), type=type, owner__isnull=True,
- ).first()
-
- if ticket:
- ticket.owner = request.user
- ticket.save()
- return Response(TicketSerializer(ticket).data)
- else:
- return Response(
- {"detail": f"No tickets of type {type} left!"},
- status=status.HTTP_403_FORBIDDEN,
- )
+ ticket = self.get_object()
+ qr_image = ticket.get_qr()
+ response = HttpResponse(content_type="image/png")
+ qr_image.save(response, "PNG")
+ return response
def get_queryset(self):
return Ticket.objects.filter(owner=self.request.user.id)
+ # def get_object(self, request, pk=None):
+ # print(pk)
+ # return Ticket.objects.get(pk=pk)
+
class MemberInviteViewSet(viewsets.ModelViewSet):
"""
@@ -4591,7 +4653,9 @@ def question_response(self, *args, **kwargs):
}
)
submission = ApplicationSubmission.objects.create(
- user=self.request.user, application=application, committee=committee,
+ user=self.request.user,
+ application=application,
+ committee=committee,
)
for question_pk in questions:
question = ApplicationQuestion.objects.filter(pk=question_pk).first()
@@ -4612,7 +4676,9 @@ def question_response(self, *args, **kwargs):
text = question_data.get("text", None)
if text is not None and text != "":
obj = ApplicationQuestionResponse.objects.create(
- text=text, question=question, submission=submission,
+ text=text,
+ question=question,
+ submission=submission,
).save()
response = Response(ApplicationQuestionResponseSerializer(obj).data)
elif question_type == ApplicationQuestion.MULTIPLE_CHOICE:
diff --git a/backend/templates/emails/ticket_confirmation.html b/backend/templates/emails/ticket_confirmation.html
new file mode 100644
index 000000000..7b2991e64
--- /dev/null
+++ b/backend/templates/emails/ticket_confirmation.html
@@ -0,0 +1,49 @@
+
+
+{% extends 'emails/base.html' %}
+
+{% block content %}
+Thanks for using Penn Clubs!
+
+
+ {{ first_name }}, thank you for your recent purchase of a ticket to {{ name }} with ticket type {{type }}.
+
+
+
+ As a reminder, the event starts at {{ start_time }} and ends at {{ end_time }}.
+
+
+
+
+
+ Please be 10 minutes early for a smooth seating experience.
+
+
+ Below is a
+ QR code for
+ your confirmation.
+
+
+
+
+ Note: all tickets issued by us are non-refundable and non-transferable .
+
+
+
+
+ If you have any questions, feel free to respond to this email.
+
+{% endblock %}
\ No newline at end of file
From 7240a1126d6b739aea224e19304e6a6d9979fca1 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Mon, 17 Jan 2022 17:14:07 -0500
Subject: [PATCH 06/64] remove commented stuff
---
backend/clubs/views.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 9cc11866b..194e54e48 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -4430,10 +4430,6 @@ def qr(self, request, *args, **kwargs):
def get_queryset(self):
return Ticket.objects.filter(owner=self.request.user.id)
- # def get_object(self, request, pk=None):
- # print(pk)
- # return Ticket.objects.get(pk=pk)
-
class MemberInviteViewSet(viewsets.ModelViewSet):
"""
From ae9944648c269aac0fb75d2423376546b8edc905 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sat, 12 Nov 2022 14:58:58 -0500
Subject: [PATCH 07/64] created cart model and add to cart, validate cart, and
checkout from cart views
---
backend/clubs/admin.py | 2 +
.../migrations/0092_auto_20220211_1732.py | 55 ++++++++
backend/clubs/models.py | 13 ++
backend/clubs/views.py | 119 +++++++++++++++++-
4 files changed, 186 insertions(+), 3 deletions(-)
create mode 100644 backend/clubs/migrations/0092_auto_20220211_1732.py
diff --git a/backend/clubs/admin.py b/backend/clubs/admin.py
index 5a0f0acb5..9dcd1c7c5 100644
--- a/backend/clubs/admin.py
+++ b/backend/clubs/admin.py
@@ -20,6 +20,7 @@
ApplicationSubmission,
Asset,
Badge,
+ Cart,
Club,
ClubApplication,
ClubFair,
@@ -445,3 +446,4 @@ class ZoomMeetingVisitAdmin(admin.ModelAdmin):
admin.site.register(ZoomMeetingVisit, ZoomMeetingVisitAdmin)
admin.site.register(AdminNote)
admin.site.register(Ticket)
+admin.site.register(Cart)
diff --git a/backend/clubs/migrations/0092_auto_20220211_1732.py b/backend/clubs/migrations/0092_auto_20220211_1732.py
new file mode 100644
index 000000000..ee9adba59
--- /dev/null
+++ b/backend/clubs/migrations/0092_auto_20220211_1732.py
@@ -0,0 +1,55 @@
+# Generated by Django 3.2.8 on 2022-02-11 22:32
+
+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),
+ ("clubs", "0091_ticket"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="ticket",
+ name="held",
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name="ticket",
+ name="holding_expiration",
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ migrations.CreateModel(
+ name="Cart",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "owner",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="cart",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ ),
+ migrations.AddField(
+ model_name="ticket",
+ name="carts",
+ field=models.ManyToManyField(
+ blank=True, related_name="tickets", to="clubs.Cart"
+ ),
+ ),
+ ]
diff --git a/backend/clubs/models.py b/backend/clubs/models.py
index 5a8091dae..aa6183c12 100644
--- a/backend/clubs/models.py
+++ b/backend/clubs/models.py
@@ -1706,6 +1706,16 @@ class QuestionResponse(models.Model):
response = models.TextField(blank=True)
+class Cart(models.Model):
+ """
+ Represents an instance of a ticket cart for a user
+ """
+
+ owner = models.ForeignKey(
+ get_user_model(), related_name="cart", on_delete=models.CASCADE
+ )
+
+
class Ticket(models.Model):
"""
Represents an instance of a ticket for an event
@@ -1723,6 +1733,9 @@ class Ticket(models.Model):
blank=True,
null=True,
)
+ held = models.BooleanField(default=False)
+ holding_expiration = models.DateTimeField(null=True, blank=True)
+ carts = models.ManyToManyField(Cart, related_name="tickets", blank=True)
def get_qr(self):
"""
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 194e54e48..e5662dfc4 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -69,6 +69,7 @@
ApplicationSubmission,
Asset,
Badge,
+ Cart,
Club,
ClubApplication,
ClubFair,
@@ -2227,6 +2228,116 @@ def get_serializer_class(self):
return EventWriteSerializer
return EventSerializer
+ @action(detail=True, methods=["post"])
+ def cart(self, request, *args, **kwargs):
+ """
+ Add a certain number of tickets to cart
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ count:
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/Ticket"
+ ---
+ """
+ type = request.data.get("type")
+ count = request.data.get("count")
+ event = Event.objects.get(id=self.get_object().id)
+ cart = Cart.objects.filter(owner=self.request.user).first()
+
+ # Try to get count unowned ticket of requested type
+ tickets = Ticket.objects.filter(
+ event=event, type=type, owner__isnull=True, held=False
+ ).exclude(carts__owner=self.request.user)
+ print(tickets.count())
+ if tickets.count() < count:
+ return Response(
+ {"detail": f"Not enough tickets of type {type} left!"},
+ status=status.HTTP_403_FORBIDDEN,
+ )
+ else:
+ for ticket in tickets[:count]:
+ ticket.carts.add(cart)
+ ticket.save()
+ return Response({"detail": "Successfully added to cart"})
+
+ @action(detail=True, methods=["post"])
+ def validate_cart(self, request, *args, **kwargs):
+ """
+ Validate tickets in a cart
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/Ticket"
+ ---
+ """
+ cart = Cart.objects.filter(owner=self.request.user).first()
+
+ for ticket in cart.tickets.all():
+ if ticket.owner or ticket.held:
+ new_ticket = Ticket.objects.filter(
+ event=ticket.event, type=ticket.type, owner__isnull=True, held=False
+ ).first()
+ cart.tickets.remove(ticket)
+ if new_ticket:
+ cart.tickets.add(new_ticket)
+ return Response({"detail": "Cart validated"})
+
+ @action(detail=True, methods=["post"])
+ def checkout(self, request, *args, **kwargs):
+ """
+ Checkout all tickets in cart, assumes all tickets are unowned and unheld
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/Ticket"
+ ---
+ """
+ cart = self.request.user.cart
+
+ # Before Stripe call, need to implement 10 minute hold here
+ for ticket in cart.tickets:
+ ticket.held = True
+
+ # If Stripe call succeeds
+ for ticket in cart.tickets:
+ ticket.owner = request.user
+ ticket.save()
+ ticket.send_confirmation_email()
+ return Response(TicketSerializer(ticket).data)
+
@action(detail=True, methods=["post"])
def buy(self, request, *args, **kwargs):
"""
@@ -2261,12 +2372,14 @@ def buy(self, request, *args, **kwargs):
# Otherwise get first unowned ticket of requested type
ticket = Ticket.objects.filter(
- event=event,
- type=type,
- owner__isnull=True,
+ event=event, type=type, owner__isnull=True, held=False
).first()
+ # Stripe call here
+
+ # If Stripe call succeeds
if ticket:
+ ticket.held = True
ticket.owner = request.user
ticket.save()
ticket.send_confirmation_email()
From 0a975fb49e8053039ebdb23b6cb2d5cea78739e2 Mon Sep 17 00:00:00 2001
From: dfeng678
Date: Sun, 20 Feb 2022 13:28:15 -0500
Subject: [PATCH 08/64] added holding expiration functionality, holding is
initiated at checkout and updated when any cart is validated before checkout
---
backend/clubs/views.py | 30 ++++++++++++++++++------------
1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index e5662dfc4..0a369fe51 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -2261,7 +2261,6 @@ def cart(self, request, *args, **kwargs):
tickets = Ticket.objects.filter(
event=event, type=type, owner__isnull=True, held=False
).exclude(carts__owner=self.request.user)
- print(tickets.count())
if tickets.count() < count:
return Response(
{"detail": f"Not enough tickets of type {type} left!"},
@@ -2291,8 +2290,16 @@ def validate_cart(self, request, *args, **kwargs):
- $ref: "#/components/schemas/Ticket"
---
"""
+ # Update holding expirations for all tickets
+ for ticket in Ticket.objects.all():
+ if ticket.holding_expiration:
+ if ticket.holding_expiration <= timezone.now():
+ ticket.held = False
+ ticket.save()
+
cart = Cart.objects.filter(owner=self.request.user).first()
+ # Checks every ticket in cart, if held or owned, tries to replace with another ticket of the same type
for ticket in cart.tickets.all():
if ticket.owner or ticket.held:
new_ticket = Ticket.objects.filter(
@@ -2301,6 +2308,7 @@ def validate_cart(self, request, *args, **kwargs):
cart.tickets.remove(ticket)
if new_ticket:
cart.tickets.add(new_ticket)
+ cart.save()
return Response({"detail": "Cart validated"})
@action(detail=True, methods=["post"])
@@ -2312,10 +2320,6 @@ def checkout(self, request, *args, **kwargs):
content:
application/json:
schema:
- type: object
- properties:
- type:
- type: string
responses:
"200":
content:
@@ -2325,18 +2329,20 @@ def checkout(self, request, *args, **kwargs):
- $ref: "#/components/schemas/Ticket"
---
"""
- cart = self.request.user.cart
+ cart = Cart.objects.filter(owner=self.request.user).first()
- # Before Stripe call, need to implement 10 minute hold here
- for ticket in cart.tickets:
+ for ticket in cart.tickets.all():
ticket.held = True
+ ticket.holding_expiration = timezone.now() + datetime.timedelta(minutes = 1)
+ ticket.save()
- # If Stripe call succeeds
- for ticket in cart.tickets:
+ # Should only run if Stripe call succeeds
+ for ticket in cart.tickets.all():
ticket.owner = request.user
+ ticket.carts.remove(cart)
ticket.save()
- ticket.send_confirmation_email()
- return Response(TicketSerializer(ticket).data)
+ # ticket.send_confirmation_email()
+ return Response({"detail": "Successfully checked out!"})
@action(detail=True, methods=["post"])
def buy(self, request, *args, **kwargs):
From 7fe194dfb339823f45fc2f0b0eb99e95a03526dc Mon Sep 17 00:00:00 2001
From: dfeng678
Date: Sun, 20 Feb 2022 13:45:52 -0500
Subject: [PATCH 09/64] lint and tests
---
backend/clubs/views.py | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 0a369fe51..bd98225fa 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -2228,7 +2228,7 @@ def get_serializer_class(self):
return EventWriteSerializer
return EventSerializer
- @action(detail=True, methods=["post"])
+ @action(detail=False, methods=["post"])
def cart(self, request, *args, **kwargs):
"""
Add a certain number of tickets to cart
@@ -2272,7 +2272,7 @@ def cart(self, request, *args, **kwargs):
ticket.save()
return Response({"detail": "Successfully added to cart"})
- @action(detail=True, methods=["post"])
+ @action(detail=False, methods=["post"])
def validate_cart(self, request, *args, **kwargs):
"""
Validate tickets in a cart
@@ -2296,10 +2296,9 @@ def validate_cart(self, request, *args, **kwargs):
if ticket.holding_expiration <= timezone.now():
ticket.held = False
ticket.save()
-
+
cart = Cart.objects.filter(owner=self.request.user).first()
- # Checks every ticket in cart, if held or owned, tries to replace with another ticket of the same type
for ticket in cart.tickets.all():
if ticket.owner or ticket.held:
new_ticket = Ticket.objects.filter(
@@ -2311,7 +2310,7 @@ def validate_cart(self, request, *args, **kwargs):
cart.save()
return Response({"detail": "Cart validated"})
- @action(detail=True, methods=["post"])
+ @action(detail=False, methods=["post"])
def checkout(self, request, *args, **kwargs):
"""
Checkout all tickets in cart, assumes all tickets are unowned and unheld
@@ -2333,7 +2332,7 @@ def checkout(self, request, *args, **kwargs):
for ticket in cart.tickets.all():
ticket.held = True
- ticket.holding_expiration = timezone.now() + datetime.timedelta(minutes = 1)
+ ticket.holding_expiration = timezone.now() + datetime.timedelta(minutes=10)
ticket.save()
# Should only run if Stripe call succeeds
From ac0975a73ff1884393c46b8d13b7e985c1f77502 Mon Sep 17 00:00:00 2001
From: dfeng678
Date: Sun, 20 Feb 2022 13:55:32 -0500
Subject: [PATCH 10/64] added cart creation for a user upon adding to a cart if
a cart does not yet exist
---
backend/clubs/views.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index bd98225fa..7dd086c38 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -2228,7 +2228,7 @@ def get_serializer_class(self):
return EventWriteSerializer
return EventSerializer
- @action(detail=False, methods=["post"])
+ @action(detail=True, methods=["post"])
def cart(self, request, *args, **kwargs):
"""
Add a certain number of tickets to cart
@@ -2256,6 +2256,9 @@ def cart(self, request, *args, **kwargs):
count = request.data.get("count")
event = Event.objects.get(id=self.get_object().id)
cart = Cart.objects.filter(owner=self.request.user).first()
+ if not cart:
+ new_cart = Cart(owner=self.request.user)
+ new_cart.save()
# Try to get count unowned ticket of requested type
tickets = Ticket.objects.filter(
From c69254aa81ea8ffff9ea52ef5b20722a7f59043c Mon Sep 17 00:00:00 2001
From: dfeng678
Date: Sun, 27 Feb 2022 13:09:51 -0500
Subject: [PATCH 11/64] slightly changed holding updates, added update to
holding status when adding to cart
---
backend/clubs/views.py | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 7dd086c38..f318a2efa 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -2252,6 +2252,12 @@ def cart(self, request, *args, **kwargs):
- $ref: "#/components/schemas/Ticket"
---
"""
+ # Update holding status for all held tickets
+ held_tickets = Ticket.objects.filter(held=True).all()
+ for ticket in held_tickets:
+ if ticket.holding_expiration <= timezone.now():
+ ticket.held = False
+ ticket.save()
type = request.data.get("type")
count = request.data.get("count")
event = Event.objects.get(id=self.get_object().id)
@@ -2293,12 +2299,12 @@ def validate_cart(self, request, *args, **kwargs):
- $ref: "#/components/schemas/Ticket"
---
"""
- # Update holding expirations for all tickets
- for ticket in Ticket.objects.all():
- if ticket.holding_expiration:
- if ticket.holding_expiration <= timezone.now():
- ticket.held = False
- ticket.save()
+ # Update holding status for all held tickets
+ held_tickets = Ticket.objects.filter(held=True).all()
+ for ticket in held_tickets:
+ if ticket.holding_expiration <= timezone.now():
+ ticket.held = False
+ ticket.save()
cart = Cart.objects.filter(owner=self.request.user).first()
From 5382f48cce5cafca7c65dc3f2a953bc10781cf68 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sat, 12 Nov 2022 15:00:56 -0500
Subject: [PATCH 12/64] atomic transactions, cleanup, and some views
---
backend/clubs/models.py | 12 +-
backend/clubs/serializers.py | 4 +
backend/clubs/views.py | 274 +++++++++++++++++++++++------------
3 files changed, 192 insertions(+), 98 deletions(-)
diff --git a/backend/clubs/models.py b/backend/clubs/models.py
index aa6183c12..8fd25c9f4 100644
--- a/backend/clubs/models.py
+++ b/backend/clubs/models.py
@@ -1711,7 +1711,7 @@ class Cart(models.Model):
Represents an instance of a ticket cart for a user
"""
- owner = models.ForeignKey(
+ owner = models.OneToOneField(
get_user_model(), related_name="cart", on_delete=models.CASCADE
)
@@ -1728,12 +1728,18 @@ class Ticket(models.Model):
type = models.CharField(max_length=100)
owner = models.ForeignKey(
get_user_model(),
- related_name="tickets",
+ related_name="owned_tickets",
+ on_delete=models.SET_NULL,
+ blank=True,
+ null=True,
+ )
+ holder = models.ForeignKey(
+ get_user_model(),
+ related_name="held_tickets",
on_delete=models.SET_NULL,
blank=True,
null=True,
)
- held = models.BooleanField(default=False)
holding_expiration = models.DateTimeField(null=True, blank=True)
carts = models.ManyToManyField(Cart, related_name="tickets", blank=True)
diff --git a/backend/clubs/serializers.py b/backend/clubs/serializers.py
index 69ec913ad..fe4ec4452 100644
--- a/backend/clubs/serializers.py
+++ b/backend/clubs/serializers.py
@@ -340,8 +340,12 @@ class ClubEventSerializer(serializers.ModelSerializer):
image_url = serializers.SerializerMethodField("get_image_url")
large_image_url = serializers.SerializerMethodField("get_large_image_url")
url = serializers.SerializerMethodField("get_event_url")
+ ticketed = serializers.SerializerMethodField("get_ticketed")
creator = serializers.HiddenField(default=serializers.CurrentUserDefault())
+ def get_ticketed(self, obj):
+ return Event.tickets.exists()
+
def get_event_url(self, obj):
# if no url, return that
if not obj.url:
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index f318a2efa..85c5e2a6a 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -25,18 +25,11 @@
from django.core.management import call_command, get_commands, load_command_class
from django.core.serializers.json import DjangoJSONEncoder
from django.core.validators import validate_email
-from django.db.models import (
- Count,
- DurationField,
- ExpressionWrapper,
- F,
- Prefetch,
- Q,
- TextField,
- Value,
-)
-from django.db.models.expressions import RawSQL
-from django.db.models.functions import SHA1, Concat, Lower, Trunc
+from django.db import transaction
+from django.db.models import Count, DurationField, ExpressionWrapper, F, Prefetch, Q
+from django.db.models.expressions import RawSQL, Value
+from django.db.models.functions import Lower, Trunc
+from django.db.models.functions.text import Concat
from django.db.models.query import prefetch_related_objects
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
@@ -2228,10 +2221,39 @@ def get_serializer_class(self):
return EventWriteSerializer
return EventSerializer
+ def update_holds(self):
+ """
+ Update ticket holds for *all* tickets
+ """
+ held_tickets = Ticket.objects.filter(holder__isnull=False).all()
+ for ticket in held_tickets:
+ if ticket.holding_expiration <= timezone.now():
+ ticket.holder = None
+ ticket.save()
+
+ @action(detail=False, methods=["get"])
+ def view_cart(self):
+ """
+ View contents of the cart
+ ---
+ requestBody: {}
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/Cart"
+
+ """
+ cart = Cart.objects.get_or_create(owner=self.request.user)
+ return None # Response(CartSerializer(cart.data))
+
@action(detail=True, methods=["post"])
- def cart(self, request, *args, **kwargs):
+ @transaction.atomic
+ def add_to_cart(self, request, *args, **kwargs):
"""
- Add a certain number of tickets to cart
+ Add a certain number of tickets to the cart
---
requestBody:
content:
@@ -2248,40 +2270,84 @@ def cart(self, request, *args, **kwargs):
content:
application/json:
schema:
- allOf:
- - $ref: "#/components/schemas/Ticket"
+ properties:
+ detail:
+ type: string
+ "403":
+ content:
+ application/json:
+ schema:
+ properties:
+ detail:
+ type: string
---
"""
- # Update holding status for all held tickets
- held_tickets = Ticket.objects.filter(held=True).all()
- for ticket in held_tickets:
- if ticket.holding_expiration <= timezone.now():
- ticket.held = False
- ticket.save()
+ self.update_holds()
type = request.data.get("type")
count = request.data.get("count")
- event = Event.objects.get(id=self.get_object().id)
- cart = Cart.objects.filter(owner=self.request.user).first()
- if not cart:
- new_cart = Cart(owner=self.request.user)
- new_cart.save()
-
- # Try to get count unowned ticket of requested type
- tickets = Ticket.objects.filter(
- event=event, type=type, owner__isnull=True, held=False
- ).exclude(carts__owner=self.request.user)
+ event = self.get_object()
+ cart = Cart.objects.get_or_create(owner=self.request.user)
+
+ # count unowned/unheld tickets of requested type
+ tickets = (
+ Ticket.objects.select_for_update(skip_locked=True)
+ .filter(event=event, type=type, owner__isnull=True, holder__isnull=True)
+ .exclude(carts__owner=self.request.user)
+ )
+
if tickets.count() < count:
return Response(
{"detail": f"Not enough tickets of type {type} left!"},
status=status.HTTP_403_FORBIDDEN,
)
else:
- for ticket in tickets[:count]:
- ticket.carts.add(cart)
- ticket.save()
+ cart.tickets.add(*tickets[:count])
+ cart.save()
return Response({"detail": "Successfully added to cart"})
+ @action(detail=True, methods=["post"])
+ @transaction.atomic
+ def remove_from_cart(self, request, *args, **kwargs):
+ """
+ Remove a certain type/number of tickets from the cart
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ count:
+ type: integer
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ properties:
+ detail:
+ type: string
+ ---
+ """
+
+ self.update_holds()
+ type = request.data.get("type")
+ event = self.get_object()
+ cart = get_object_or_404(owner=self.request.user)
+ tickets = cart.tickets.filter(type=type, event=event)
+
+ # Ensure we don't try to remove more tickets than we can
+ count = min(request.data.get("count"), tickets.count())
+
+ cart.tickets.remove(*tickets[:count])
+ cart.save()
+
+ return Response({"detail": "Successfully removed from cart"})
+
@action(detail=False, methods=["post"])
+ @transaction.atomic
def validate_cart(self, request, *args, **kwargs):
"""
Validate tickets in a cart
@@ -2299,72 +2365,112 @@ def validate_cart(self, request, *args, **kwargs):
- $ref: "#/components/schemas/Ticket"
---
"""
- # Update holding status for all held tickets
- held_tickets = Ticket.objects.filter(held=True).all()
- for ticket in held_tickets:
- if ticket.holding_expiration <= timezone.now():
- ticket.held = False
- ticket.save()
+ self.update_holds()
- cart = Cart.objects.filter(owner=self.request.user).first()
+ cart = get_object_or_404(Cart, owner=self.request.user)
+ sold_out_flag = False
for ticket in cart.tickets.all():
- if ticket.owner or ticket.held:
- new_ticket = Ticket.objects.filter(
- event=ticket.event, type=ticket.type, owner__isnull=True, held=False
- ).first()
+ # if ticket in cart has been bought, try to replace
+ if ticket.owner or ticket.holder:
+ # lock new ticket until transaction is completed
+ new_ticket = (
+ Ticket.objects.select_for_update(skip_locked=True)
+ .filter(
+ event=ticket.event,
+ type=ticket.type,
+ owner__isnull=True,
+ holder__isnull=True,
+ )
+ .first()
+ )
cart.tickets.remove(ticket)
if new_ticket:
cart.tickets.add(new_ticket)
- cart.save()
- return Response({"detail": "Cart validated"})
+ else:
+ sold_out_flag = True
+ cart.save()
+
+ return Response(
+ {"detail": "Validated" + ("" if not sold_out_flag else " with changes")}
+ )
@action(detail=False, methods=["post"])
+ @transaction.atomic
def checkout(self, request, *args, **kwargs):
"""
- Checkout all tickets in cart, assumes all tickets are unowned and unheld
+ Checkout all tickets in cart, to be called after validate_cart
+
+ NOTE: this does NOT buy tickets, it simply initiates a checkout process
+ which includes a 10-minute ticket hold
---
requestBody:
content:
application/json:
schema:
+ type: object
+
responses:
"200":
content:
application/json:
schema:
- allOf:
- - $ref: "#/components/schemas/Ticket"
+ properties:
+ detail:
+ type: string
---
"""
- cart = Cart.objects.filter(owner=self.request.user).first()
+ cart = get_object_or_404(Cart, owner=self.request.user)
- for ticket in cart.tickets.all():
- ticket.held = True
+ # The assumption is that this filter query should return all tickets in the cart (if run after validate_cart);
+ # however we cannot guarantee atomicity between validate_cart and checkout
+ #
+ # customers will be prompted to review the cart before payment
+
+ for ticket in cart.tickets.select_for_update().filter(
+ owner__isnull=True, holder__isnull=True
+ ):
+ ticket.holder = self.request.user
ticket.holding_expiration = timezone.now() + datetime.timedelta(minutes=10)
ticket.save()
- # Should only run if Stripe call succeeds
- for ticket in cart.tickets.all():
+ return Response({"detail": "Successfully initated checkout"})
+
+ @action(detail=False, methods=["post"])
+ @transaction.atomic
+ def checkout_success_callback(self, request, *args, **kwargs):
+ """
+ Callback after third party payment succeeds
+ ---
+ requestBody: {}
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ properties:
+ detail:
+ type: string
+
+ """
+ cart = get_object_or_404(Cart, owner=self.request.user)
+
+ for ticket in cart.tickets.select_for_update().all():
ticket.owner = request.user
ticket.carts.remove(cart)
- ticket.save()
# ticket.send_confirmation_email()
- return Response({"detail": "Successfully checked out!"})
+ ticket.save()
+
+ return Response({"detail": "callback successful"})
@action(detail=True, methods=["post"])
+ @transaction.atomic
def buy(self, request, *args, **kwargs):
"""
- Buy a ticket
+ Buy tickets in a cart
---
requestBody:
- content:
- application/json:
- schema:
- type: object
- properties:
- type:
- type: string
+ content: {}
responses:
"200":
content:
@@ -2374,35 +2480,16 @@ def buy(self, request, *args, **kwargs):
- $ref: "#/components/schemas/Ticket"
---
"""
- type = request.data.get("type")
- event = self.get_object()
- # If ticket already owned, do nothing
- if Ticket.objects.filter(event=event, owner=request.user.id).first():
- return Response(
- {"detail": "Ticket to event already owned by user"},
- status=status.HTTP_400_BAD_REQUEST,
- )
+ # Some logic here to serialize all held tickets down to whatever
+ # format third party asks for
- # Otherwise get first unowned ticket of requested type
- ticket = Ticket.objects.filter(
- event=event, type=type, owner__isnull=True, held=False
- ).first()
+ cart = get_object_or_404(Cart, owner=self.request.user)
- # Stripe call here
+ for ticket in cart.tickets.filter(holder=self.request.user):
+ pass
- # If Stripe call succeeds
- if ticket:
- ticket.held = True
- ticket.owner = request.user
- ticket.save()
- ticket.send_confirmation_email()
- return Response(TicketSerializer(ticket).data)
- else:
- return Response(
- {"detail": f"No tickets of type {type} left!"},
- status=status.HTTP_403_FORBIDDEN,
- )
+ pass
@action(detail=True, methods=["get"])
def tickets(self, request, *args, **kwargs):
@@ -2478,8 +2565,6 @@ def create_tickets(self, request, *args, **kwargs):
schema:
type: object
properties:
- name:
- type: string
quantities:
type: array
items:
@@ -2498,7 +2583,7 @@ def create_tickets(self, request, *args, **kwargs):
properties:
detail:
type: string
- description: A success or error message.
+ description: Empty array for success
"""
event = self.get_object()
quantities = request.data.get("quantities")
@@ -2536,7 +2621,6 @@ def upload(self, request, *args, **kwargs):
description: Returned if the file was successfully uploaded.
content: &upload_resp
application/json:
- schema:
type: object
properties:
detail:
From 6508a979391dd0ae6646b9862c97a8a81797a7cc Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Wed, 6 Jul 2022 04:10:47 -0700
Subject: [PATCH 13/64] holding, validation improvements, perms
---
backend/clubs/migrations/0091_cart_ticket.py | 90 ++++
backend/clubs/migrations/0091_ticket.py | 51 ---
backend/clubs/models.py | 14 +
backend/clubs/permissions.py | 11 +-
backend/clubs/serializers.py | 3 +-
backend/clubs/views.py | 431 ++++++++++---------
6 files changed, 346 insertions(+), 254 deletions(-)
create mode 100644 backend/clubs/migrations/0091_cart_ticket.py
delete mode 100644 backend/clubs/migrations/0091_ticket.py
diff --git a/backend/clubs/migrations/0091_cart_ticket.py b/backend/clubs/migrations/0091_cart_ticket.py
new file mode 100644
index 000000000..758aa2e30
--- /dev/null
+++ b/backend/clubs/migrations/0091_cart_ticket.py
@@ -0,0 +1,90 @@
+# Generated by Django 3.2.8 on 2022-10-02 16:50
+
+import uuid
+
+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),
+ ("clubs", "0090_adminnote"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Cart",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "owner",
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="cart",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ ),
+ migrations.CreateModel(
+ name="Ticket",
+ fields=[
+ (
+ "id",
+ models.UUIDField(
+ default=uuid.uuid4,
+ editable=False,
+ primary_key=True,
+ serialize=False,
+ ),
+ ),
+ ("type", models.CharField(max_length=100)),
+ ("holding_expiration", models.DateTimeField(blank=True, null=True)),
+ (
+ "carts",
+ models.ManyToManyField(
+ blank=True, related_name="tickets", to="clubs.Cart"
+ ),
+ ),
+ (
+ "event",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="tickets",
+ to="clubs.event",
+ ),
+ ),
+ (
+ "holder",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="held_tickets",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ (
+ "owner",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="owned_tickets",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ ),
+ ]
diff --git a/backend/clubs/migrations/0091_ticket.py b/backend/clubs/migrations/0091_ticket.py
deleted file mode 100644
index a76becfee..000000000
--- a/backend/clubs/migrations/0091_ticket.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Generated by Django 3.2.8 on 2022-01-16 20:59
-
-import uuid
-
-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),
- ("clubs", "0090_adminnote"),
- ]
-
- operations = [
- migrations.CreateModel(
- name="Ticket",
- fields=[
- (
- "id",
- models.UUIDField(
- default=uuid.uuid4,
- editable=False,
- primary_key=True,
- serialize=False,
- ),
- ),
- ("type", models.CharField(max_length=100)),
- (
- "event",
- models.ForeignKey(
- on_delete=django.db.models.deletion.DO_NOTHING,
- related_name="tickets",
- to="clubs.event",
- ),
- ),
- (
- "owner",
- models.ForeignKey(
- blank=True,
- null=True,
- on_delete=django.db.models.deletion.SET_NULL,
- related_name="tickets",
- to=settings.AUTH_USER_MODEL,
- ),
- ),
- ],
- ),
- ]
diff --git a/backend/clubs/models.py b/backend/clubs/models.py
index 8fd25c9f4..a9f90de55 100644
--- a/backend/clubs/models.py
+++ b/backend/clubs/models.py
@@ -1716,6 +1716,19 @@ class Cart(models.Model):
)
+class TicketManager(models.Manager):
+
+ # Update holds for all tickets
+ def update_holds(self):
+ expired_tickets = self.select_for_update().filter(
+ holder__isnull=False, holding_expiration__lte=timezone.now()
+ )
+ with transaction.atomic():
+ for ticket in expired_tickets:
+ ticket.holder = None
+ self.bulk_update(expired_tickets, ["holder"])
+
+
class Ticket(models.Model):
"""
Represents an instance of a ticket for an event
@@ -1742,6 +1755,7 @@ class Ticket(models.Model):
)
holding_expiration = models.DateTimeField(null=True, blank=True)
carts = models.ManyToManyField(Cart, related_name="tickets", blank=True)
+ objects = TicketManager()
def get_qr(self):
"""
diff --git a/backend/clubs/permissions.py b/backend/clubs/permissions.py
index 09117f98a..4297d9fcc 100644
--- a/backend/clubs/permissions.py
+++ b/backend/clubs/permissions.py
@@ -188,7 +188,7 @@ def has_permission(self, request, view):
class EventPermission(permissions.BasePermission):
"""
- Officers and above can create/update/delete events.
+ Officers and above can create/update/delete events and view ticket buyers.
Everyone else can view and list events.
"""
@@ -224,7 +224,14 @@ def has_object_permission(self, request, view, obj):
if not old_type == FAIR_TYPE and new_type == FAIR_TYPE:
return False
-
+ elif view.action in ["buyers", "create_tickets"]:
+ if not request.user.is_authenticated:
+ return False
+ membership = find_membership_helper(request.user, obj.club)
+ return membership is not None and membership.role <= Membership.ROLE_OFFICER
+ elif view.action in ["add_to_cart", "remove_from_cart"]:
+ return request.user.is_authenticated
+ print("action", view.action)
return True
diff --git a/backend/clubs/serializers.py b/backend/clubs/serializers.py
index fe4ec4452..9e3286308 100644
--- a/backend/clubs/serializers.py
+++ b/backend/clubs/serializers.py
@@ -343,7 +343,7 @@ class ClubEventSerializer(serializers.ModelSerializer):
ticketed = serializers.SerializerMethodField("get_ticketed")
creator = serializers.HiddenField(default=serializers.CurrentUserDefault())
- def get_ticketed(self, obj):
+ def get_ticketed(self, obj) -> bool:
return Event.tickets.exists()
def get_event_url(self, obj):
@@ -483,6 +483,7 @@ class Meta:
"location",
"name",
"start_time",
+ "ticketed",
"type",
"url",
]
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 85c5e2a6a..e7b259fc7 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -8,6 +8,7 @@
import re
import secrets
import string
+from functools import wraps
from urllib.parse import urlparse
import pytz
@@ -171,6 +172,19 @@
from clubs.utils import fuzzy_lookup_club, html_to_text
+def update_holds(func):
+ """
+ Decorator to update ticket holds
+ """
+
+ @wraps(func)
+ def wrap(self, request, *args, **kwargs):
+ Ticket.objects.update_holds()
+ return func(self, request, *args, **kwargs)
+
+ return wrap
+
+
def file_upload_endpoint_helper(request, code):
obj = get_object_or_404(Club, code=code)
if "file" in request.data and isinstance(request.data["file"], UploadedFile):
@@ -1561,7 +1575,6 @@ def booths(self, request, *args, **kwargs):
"""
club = self.get_object()
res = ClubFairBooth.objects.filter(club=club).select_related("club").all()
-
return Response(ClubBoothSerializer(res, many=True).data)
def get_operation_id(self, **kwargs):
@@ -2201,6 +2214,16 @@ class ClubEventViewSet(viewsets.ModelViewSet):
buy:
Buy a ticket for an event
+
+ buyers:
+ Get information about the buyers of an event's ticket
+
+ remove_from_cart:
+ Remove a ticket for this event from cart
+
+ add_to_cart:
+ Add a ticket for this event to cart
+
"""
permission_classes = [EventPermission | IsSuperuser]
@@ -2221,36 +2244,9 @@ def get_serializer_class(self):
return EventWriteSerializer
return EventSerializer
- def update_holds(self):
- """
- Update ticket holds for *all* tickets
- """
- held_tickets = Ticket.objects.filter(holder__isnull=False).all()
- for ticket in held_tickets:
- if ticket.holding_expiration <= timezone.now():
- ticket.holder = None
- ticket.save()
-
- @action(detail=False, methods=["get"])
- def view_cart(self):
- """
- View contents of the cart
- ---
- requestBody: {}
- responses:
- "200":
- content:
- application/json:
- schema:
- allOf:
- - $ref: "#/components/schemas/Cart"
-
- """
- cart = Cart.objects.get_or_create(owner=self.request.user)
- return None # Response(CartSerializer(cart.data))
-
@action(detail=True, methods=["post"])
@transaction.atomic
+ @update_holds
def add_to_cart(self, request, *args, **kwargs):
"""
Add a certain number of tickets to the cart
@@ -2273,7 +2269,7 @@ def add_to_cart(self, request, *args, **kwargs):
properties:
detail:
type: string
- "403":
+ "403":
content:
application/json:
schema:
@@ -2282,13 +2278,12 @@ def add_to_cart(self, request, *args, **kwargs):
type: string
---
"""
- self.update_holds()
type = request.data.get("type")
count = request.data.get("count")
event = self.get_object()
cart = Cart.objects.get_or_create(owner=self.request.user)
- # count unowned/unheld tickets of requested type
+ # Count unowned/unheld tickets of requested type
tickets = (
Ticket.objects.select_for_update(skip_locked=True)
.filter(event=event, type=type, owner__isnull=True, holder__isnull=True)
@@ -2307,6 +2302,7 @@ def add_to_cart(self, request, *args, **kwargs):
@action(detail=True, methods=["post"])
@transaction.atomic
+ @update_holds
def remove_from_cart(self, request, *args, **kwargs):
"""
Remove a certain type/number of tickets from the cart
@@ -2332,10 +2328,9 @@ def remove_from_cart(self, request, *args, **kwargs):
---
"""
- self.update_holds()
type = request.data.get("type")
event = self.get_object()
- cart = get_object_or_404(owner=self.request.user)
+ cart = get_object_or_404(Cart, owner=self.request.user)
tickets = cart.tickets.filter(type=type, event=event)
# Ensure we don't try to remove more tickets than we can
@@ -2346,101 +2341,10 @@ def remove_from_cart(self, request, *args, **kwargs):
return Response({"detail": "Successfully removed from cart"})
- @action(detail=False, methods=["post"])
- @transaction.atomic
- def validate_cart(self, request, *args, **kwargs):
- """
- Validate tickets in a cart
- ---
- requestBody:
- content:
- application/json:
- schema:
- responses:
- "200":
- content:
- application/json:
- schema:
- allOf:
- - $ref: "#/components/schemas/Ticket"
- ---
- """
- self.update_holds()
-
- cart = get_object_or_404(Cart, owner=self.request.user)
- sold_out_flag = False
-
- for ticket in cart.tickets.all():
- # if ticket in cart has been bought, try to replace
- if ticket.owner or ticket.holder:
- # lock new ticket until transaction is completed
- new_ticket = (
- Ticket.objects.select_for_update(skip_locked=True)
- .filter(
- event=ticket.event,
- type=ticket.type,
- owner__isnull=True,
- holder__isnull=True,
- )
- .first()
- )
- cart.tickets.remove(ticket)
- if new_ticket:
- cart.tickets.add(new_ticket)
- else:
- sold_out_flag = True
- cart.save()
-
- return Response(
- {"detail": "Validated" + ("" if not sold_out_flag else " with changes")}
- )
-
- @action(detail=False, methods=["post"])
- @transaction.atomic
- def checkout(self, request, *args, **kwargs):
- """
- Checkout all tickets in cart, to be called after validate_cart
-
- NOTE: this does NOT buy tickets, it simply initiates a checkout process
- which includes a 10-minute ticket hold
- ---
- requestBody:
- content:
- application/json:
- schema:
- type: object
-
- responses:
- "200":
- content:
- application/json:
- schema:
- properties:
- detail:
- type: string
- ---
- """
- cart = get_object_or_404(Cart, owner=self.request.user)
-
- # The assumption is that this filter query should return all tickets in the cart (if run after validate_cart);
- # however we cannot guarantee atomicity between validate_cart and checkout
- #
- # customers will be prompted to review the cart before payment
-
- for ticket in cart.tickets.select_for_update().filter(
- owner__isnull=True, holder__isnull=True
- ):
- ticket.holder = self.request.user
- ticket.holding_expiration = timezone.now() + datetime.timedelta(minutes=10)
- ticket.save()
-
- return Response({"detail": "Successfully initated checkout"})
-
- @action(detail=False, methods=["post"])
- @transaction.atomic
- def checkout_success_callback(self, request, *args, **kwargs):
+ @action(detail=True, methods=["get"])
+ def buyers(self, request, *args, **kwargs):
"""
- Callback after third party payment succeeds
+ Get information about ticket buyers
---
requestBody: {}
responses:
@@ -2448,48 +2352,32 @@ def checkout_success_callback(self, request, *args, **kwargs):
content:
application/json:
schema:
- properties:
- detail:
- type: string
-
- """
- cart = get_object_or_404(Cart, owner=self.request.user)
-
- for ticket in cart.tickets.select_for_update().all():
- ticket.owner = request.user
- ticket.carts.remove(cart)
- # ticket.send_confirmation_email()
- ticket.save()
-
- return Response({"detail": "callback successful"})
-
- @action(detail=True, methods=["post"])
- @transaction.atomic
- def buy(self, request, *args, **kwargs):
- """
- Buy tickets in a cart
- ---
- requestBody:
- content: {}
- responses:
- "200":
- content:
- application/json:
- schema:
- allOf:
- - $ref: "#/components/schemas/Ticket"
+ type: object
+ properties:
+ buyers:
+ type: array
+ items:
+ type: object
+ properties:
+ fullname:
+ type: string
+ id:
+ type: string
+ owner_id:
+ type: int
+ type:
+ type: string
---
"""
+ tickets = Ticket.objects.filter(event=self.get_object()).annotate(
+ fullname=Concat("owner__first_name", Value(" "), "owner__last_name")
+ )
- # Some logic here to serialize all held tickets down to whatever
- # format third party asks for
-
- cart = get_object_or_404(Cart, owner=self.request.user)
-
- for ticket in cart.tickets.filter(holder=self.request.user):
- pass
+ buyers = tickets.filter(owner__isnull=False).values(
+ "id", "fullname", "owner_id", "type"
+ )
- pass
+ return Response({"buyers": buyers})
@action(detail=True, methods=["get"])
def tickets(self, request, *args, **kwargs):
@@ -2504,17 +2392,6 @@ def tickets(self, request, *args, **kwargs):
schema:
type: object
properties:
- buyers:
- type: array
- items:
- type: object
- properties:
- fullname:
- type: string
- id:
- type: string
- type:
- type: string
totals:
type: array
items:
@@ -2536,10 +2413,9 @@ def tickets(self, request, *args, **kwargs):
---
"""
event = self.get_object()
- tickets = Ticket.objects.filter(event=event).annotate(
- fullname=Concat("owner__first_name", Value(" "), "owner__last_name")
- )
+ tickets = Ticket.objects.filter(event=event)
types = tickets.values_list("type", flat=True).distinct()
+
totals = []
available = []
@@ -2552,11 +2428,10 @@ def tickets(self, request, *args, **kwargs):
}
)
- buyers = tickets.filter(owner__isnull=False).values("id", "fullname", "type")
-
- return Response({"totals": totals, "available": available, "buyers": buyers})
+ return Response({"totals": totals, "available": available})
@tickets.mapping.put
+ @transaction.atomic
def create_tickets(self, request, *args, **kwargs):
"""
requestBody:
@@ -2583,24 +2458,20 @@ def create_tickets(self, request, *args, **kwargs):
properties:
detail:
type: string
- description: Empty array for success
"""
event = self.get_object()
quantities = request.data.get("quantities")
- membership = find_membership_helper(request.user, event.club)
- if membership.role <= 10: # Create tickets allowed if officer+
+ Ticket.objects.filter(event=event).delete() # Idempotency
+ tickets = [
+ Ticket(event=event, type=item["type"])
+ for item in quantities
+ for _ in range(item["count"])
+ ]
- Ticket.objects.filter(event=event).delete() # Idempotency
+ Ticket.objects.bulk_create(tickets)
- for item in quantities:
- for _ in range(item["count"]):
- Ticket.objects.create(event=event, type=item["type"])
- return Response([])
- else:
- return Response(
- {"detail": "Unauthorized"}, status=status.HTTP_401_UNAUTHORIZED
- )
+ return Response({"detail": "success"})
@action(detail=True, methods=["post"])
def upload(self, request, *args, **kwargs):
@@ -4599,11 +4470,17 @@ def get_object(self):
class TicketViewSet(viewsets.ModelViewSet):
"""
- list:
+ get:
List all tickets owned by user
- retrieve:
- Retrieve an individual ticket's data
+ cart:
+ List all unowned/unheld tickets currently in user's cart
+
+ checkout:
+ Initiate a hold on the tickets in a user's cart
+
+ buy:
+ Buy the tickets in a user's cart
qr:
Get a ticket's QR code
@@ -4614,8 +4491,162 @@ class TicketViewSet(viewsets.ModelViewSet):
http_method_names = ["get", "post"]
lookup_field = "id"
- def retrieve(self, request, *args, **kwargs):
- return Response(TicketSerializer(Ticket.objects.get(id=kwargs["id"])).data)
+ @transaction.atomic
+ @update_holds
+ @action(detail=False, methods=["get"])
+ def cart(self, request, *args, **kwargs):
+ """
+ Validate tickets in a cart
+ ---
+ requestBody:
+ content: {}
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/Ticket"
+ "204":
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/Ticket"
+ ---
+ """
+
+ cart, _ = Cart.objects.get_or_create(owner=self.request.user)
+
+ # this flag is true when a validate operation fails to
+ # replace a ticket. return 200 if true, otherwise 204
+ sold_out_flag = False
+
+ for ticket in cart.tickets.all():
+ # if ticket in cart has been bought, try to replace
+ if ticket.owner or ticket.holder:
+ # lock new ticket until transaction is completed
+ new_ticket = (
+ Ticket.objects.select_for_update(skip_locked=True)
+ .filter(
+ event=ticket.event,
+ type=ticket.type,
+ owner__isnull=True,
+ holder__isnull=True,
+ )
+ .first()
+ )
+ cart.tickets.remove(ticket)
+ if new_ticket:
+ cart.tickets.add(new_ticket)
+ else:
+ sold_out_flag = True
+ cart.save()
+
+ return Response(
+ TicketSerializer(cart.tickets.all(), many=True).data,
+ status=200 if sold_out_flag else 204,
+ )
+
+ @action(detail=False, methods=["post"])
+ @update_holds
+ @transaction.atomic
+ def checkout(self, request, *args, **kwargs):
+ """
+ Checkout all tickets in cart
+
+ NOTE: this does NOT buy tickets, it simply initiates a checkout process
+ which includes a 10-minute ticket hold
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ properties:
+ detail:
+ type: string
+ ---
+ """
+ cart = get_object_or_404(Cart, owner=self.request.user)
+
+ # The assumption is that this filter query should return all tickets in the cart
+ # however we cannot guarantee atomicity between cart and checkout
+ #
+ # customers will be prompted to review the cart before payment
+
+ tickets = cart.tickets.select_for_update().filter(
+ Q(holder__isnull=True) | Q(holder=self.request.user), owner__isnull=True
+ )
+
+ for ticket in tickets:
+ ticket.holder = self.request.user
+ ticket.holding_expiration = timezone.now() + datetime.timedelta(minutes=10)
+
+ Ticket.objects.bulk_update(tickets, ["holder", "holding_expiration"])
+
+ return Response({"detail": "Successfully initated checkout"})
+
+ @action(detail=False, methods=["post"])
+ @transaction.atomic
+ def checkout_success_callback(self, request, *args, **kwargs):
+ """
+ Callback after third party payment succeeds
+ ---
+ requestBody: {}
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ properties:
+ detail:
+ type: string
+
+ """
+ cart = get_object_or_404(Cart, owner=self.request.user)
+
+ for ticket in cart.tickets.select_for_update().all():
+ ticket.owner = request.user
+ ticket.carts.clear()
+ # ticket.send_confirmation_email()
+ ticket.save()
+
+ return Response({"detail": "callback successful"})
+
+ @action(detail=False, methods=["post"])
+ @transaction.atomic
+ def buy(self, request, *args, **kwargs):
+ """
+ Buy held tickets in a cart
+ ---
+ requestBody:
+ content: {}
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/Ticket"
+ ---
+ """
+
+ # Some logic here to serialize all held tickets down to whatever
+ # format third party asks for
+
+ cart = get_object_or_404(Cart, owner=self.request.user)
+
+ for ticket in cart.tickets.filter(holder=self.request.user):
+ pass
+
+ return Response({})
@action(detail=True, methods=["get"])
def qr(self, request, *args, **kwargs):
From 961074739cdd68a5c68bdba62f689e43842dc194 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sun, 16 Oct 2022 12:40:04 -0400
Subject: [PATCH 14/64] some docstring stuff
---
backend/clubs/permissions.py | 1 -
backend/clubs/views.py | 15 ++++++++++++++-
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/backend/clubs/permissions.py b/backend/clubs/permissions.py
index 4297d9fcc..253f9d807 100644
--- a/backend/clubs/permissions.py
+++ b/backend/clubs/permissions.py
@@ -231,7 +231,6 @@ def has_object_permission(self, request, view, obj):
return membership is not None and membership.role <= Membership.ROLE_OFFICER
elif view.action in ["add_to_cart", "remove_from_cart"]:
return request.user.is_authenticated
- print("action", view.action)
return True
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index e7b259fc7..e525b32f5 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -2462,6 +2462,8 @@ def create_tickets(self, request, *args, **kwargs):
event = self.get_object()
quantities = request.data.get("quantities")
+ # Atomicity ensures idempotency
+
Ticket.objects.filter(event=event).delete() # Idempotency
tickets = [
Ticket(event=event, type=item["type"])
@@ -2657,6 +2659,12 @@ class EventViewSet(ClubEventViewSet):
destroy:
Delete an event.
+
+ fair:
+ Get information about a fair listing
+
+ owned:
+ Return all events that the user has officer permissions over.
"""
def get_operation_id(self, **kwargs):
@@ -4479,6 +4487,9 @@ class TicketViewSet(viewsets.ModelViewSet):
checkout:
Initiate a hold on the tickets in a user's cart
+ checkout_success_callback:
+ Callback after third party payment succeeds
+
buy:
Buy the tickets in a user's cart
@@ -4578,7 +4589,7 @@ def checkout(self, request, *args, **kwargs):
# The assumption is that this filter query should return all tickets in the cart
# however we cannot guarantee atomicity between cart and checkout
- #
+
# customers will be prompted to review the cart before payment
tickets = cart.tickets.select_for_update().filter(
@@ -4638,6 +4649,8 @@ def buy(self, request, *args, **kwargs):
---
"""
+ # TODO: Implement
+
# Some logic here to serialize all held tickets down to whatever
# format third party asks for
From c9fb1156b0db73fa0901338adb0c84729741ad97 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sun, 16 Oct 2022 13:06:04 -0400
Subject: [PATCH 15/64] rebase + pipfile lock
---
backend/Pipfile.lock | 305 ++++++++++++++++-------------------------
backend/clubs/views.py | 2 +
2 files changed, 117 insertions(+), 190 deletions(-)
diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock
index 6c195adb4..bd497c315 100644
--- a/backend/Pipfile.lock
+++ b/backend/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "1e5ed3497a97d54da1163f9def2f5aee2feaf2aa537a361d42376f30cb8cac4f"
+ "sha256": "ebeb34dcfd58ce86a7aee2ed18042c0fb124d534ee3b168f1943f1e294212b34"
},
"pipfile-spec": 6,
"requires": {
@@ -16,13 +16,6 @@
]
},
"default": {
- "aioredis": {
- "hashes": [
- "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a",
- "sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3"
- ],
- "version": "==1.3.1"
- },
"anyio": {
"hashes": [
"sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b",
@@ -33,11 +26,11 @@
},
"arrow": {
"hashes": [
- "sha256:4bfacea734ead51495dc47df00421ecfd4ca1f2c0fbe58b9a26eaeddedc31caf",
- "sha256:67f8be7c0cf420424bc62d8d7dc40b44e4bb2f7b515f9cc2954fb36e35797656"
+ "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1",
+ "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==0.14.7"
+ "markers": "python_version >= '3.6'",
+ "version": "==1.2.3"
},
"asgiref": {
"hashes": [
@@ -82,32 +75,32 @@
"sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
"sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
],
- "markers": "python_full_version >= '3.6.0'",
+ "markers": "python_version >= '3.6'",
"version": "==4.11.1"
},
"bleach": {
"hashes": [
- "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da",
- "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"
+ "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a",
+ "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"
],
"index": "pypi",
- "version": "==4.1.0"
+ "version": "==5.0.1"
},
"boto3": {
"hashes": [
- "sha256:6194763348545bb1669ce8d03ba104be1ba822daa184613aa10b9303a6a79017",
- "sha256:be151711bbb4db53e85dd5bbe506002ce6f2f21fc4e45fcf6d2cf356d32cc4c6"
+ "sha256:3225366014949039e6687387242e73f237f0fee0a9b7c20461894f1ee40686b8",
+ "sha256:b295640bc1be637f8f7c8c8fca70781048d6397196109e59f20541824fab4b67"
],
"index": "pypi",
- "version": "==1.24.84"
+ "version": "==1.24.91"
},
"botocore": {
"hashes": [
- "sha256:11f05d2acdf9a5f722856704b7b951b180647fb4340e1b5048b27273dc323909",
- "sha256:da15026329706caf83323d84996f5ff5c527837347633fca9b3b1be0efa60841"
+ "sha256:1d6e97bd8653f732c7078b34aa2bb438e750898957e5a0a74b6c72918bc1d0f7",
+ "sha256:c8fac203a391cc2e4b682877bfce70e723e33c529b35b399a1d574605fbeb1af"
],
"markers": "python_version >= '3.7'",
- "version": "==1.27.84"
+ "version": "==1.27.91"
},
"bs4": {
"hashes": [
@@ -211,27 +204,27 @@
},
"channels-redis": {
"hashes": [
- "sha256:78e4a2f2b2a744fe5a87848ec36b5ee49f522c6808cefe6c583663d0d531faa8",
- "sha256:ba7e2ad170f273c372812dd32aaac102d68d4e508172abb1cfda3160b7333890"
+ "sha256:122414f29f525f7b9e0c9d59cdcfc4dc1b0eecba16fbb6a1c23f1d9b58f49dcb",
+ "sha256:81b59d68f53313e1aa891f23591841b684abb936b42e4d1a966d9e4dc63a95ec"
],
"index": "pypi",
- "version": "==3.4.1"
+ "version": "==4.0.0"
},
"charset-normalizer": {
"hashes": [
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
- "markers": "python_full_version >= '3.6.0'",
+ "markers": "python_version >= '3.6'",
"version": "==2.1.1"
},
"click": {
"hashes": [
- "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1",
- "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"
+ "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
+ "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
- "index": "pypi",
- "version": "==8.0.4"
+ "markers": "python_version >= '3.7'",
+ "version": "==8.1.3"
},
"conditional": {
"hashes": [
@@ -320,11 +313,11 @@
},
"django": {
"hashes": [
- "sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713",
- "sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b"
+ "sha256:26dc24f99c8956374a054bcbf58aab8dc0cad2e6ac82b0fe036b752c00eee793",
+ "sha256:b8d843714810ab88d59344507d4447be8b2cf12a49031363b6eed9f1b9b2280f"
],
"index": "pypi",
- "version": "==3.2.15"
+ "version": "==4.1.2"
},
"django-clone": {
"hashes": [
@@ -452,53 +445,6 @@
"markers": "python_version >= '3.7'",
"version": "==0.14.0"
},
- "hiredis": {
- "hashes": [
- "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e",
- "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27",
- "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163",
- "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc",
- "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26",
- "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e",
- "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579",
- "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a",
- "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048",
- "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87",
- "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63",
- "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54",
- "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05",
- "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb",
- "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea",
- "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5",
- "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e",
- "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc",
- "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99",
- "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a",
- "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581",
- "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426",
- "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db",
- "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a",
- "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a",
- "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d",
- "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443",
- "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79",
- "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d",
- "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9",
- "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d",
- "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485",
- "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5",
- "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048",
- "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0",
- "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6",
- "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41",
- "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298",
- "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce",
- "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
- "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==2.0.0"
- },
"httptools": {
"hashes": [
"sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9",
@@ -555,20 +501,20 @@
},
"ics": {
"hashes": [
- "sha256:3b606205b9582ad27dff77f9b227a30d02fdac532731927fe39df1f1ddf8673f",
- "sha256:81113a2bb3166c1afcd71cd450c968d40efc385601e9d8344733e00ad8f53429",
- "sha256:bf5fbdef6e1e073afdadf1b996f0271186dd114a148e38e795919a1ae644d6ac"
+ "sha256:5fcf4d29ec6e7dfcb84120abd617bbba632eb77b097722b7df70e48dbcf26103",
+ "sha256:6743539bca10391635249b87d74fcd1094af20b82098bebf7c7521df91209f05",
+ "sha256:da352bdf8418619dc93611e6d251f3cefbb42664777f6e00b580a722098124b7"
],
"index": "pypi",
- "version": "==0.7"
+ "version": "==0.7.2"
},
"identify": {
"hashes": [
- "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6",
- "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97"
+ "sha256:6c32dbd747aa4ceee1df33f25fed0b0f6e0d65721b15bd151307ff7056d50245",
+ "sha256:b276db7ec52d7e89f5bc4653380e33054ddc803d25875952ad90b0f012cbcdaa"
],
"markers": "python_version >= '3.7'",
- "version": "==2.5.5"
+ "version": "==2.5.6"
},
"idna": {
"hashes": [
@@ -580,10 +526,10 @@
},
"incremental": {
"hashes": [
- "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57",
- "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"
+ "sha256:912feeb5e0f7e0188e6f42241d2f450002e11bbc0937c65865045854c24c0bd0",
+ "sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51"
],
- "version": "==21.3.0"
+ "version": "==22.10.0"
},
"jmespath": {
"hashes": [
@@ -595,11 +541,11 @@
},
"jsonref": {
"hashes": [
- "sha256:b1e82fa0b62e2c2796a13e5401fe51790b248f6d9bf9d7212a3e31a3501b291f",
- "sha256:f3c45b121cf6257eafabdc3a8008763aed1cd7da06dbabc59a9e4d2a5e4e6697"
+ "sha256:68b330c6815dc0d490dbb3d65ccda265ddde9f7856fd2f3322f971d456ea7549",
+ "sha256:9480ad1b500f7e795daeb0ef29f9c55ae3a9ab38fb8d6659b6f4868acb5a5bc8"
],
"index": "pypi",
- "version": "==0.2"
+ "version": "==0.3.0"
},
"lxml": {
"hashes": [
@@ -768,11 +714,11 @@
},
"phonenumbers": {
"hashes": [
- "sha256:80a7422cf0999a6f9b7a2e6cfbdbbfcc56ab5b75414dc3b805bbec91276b64a3",
- "sha256:82a4f226c930d02dcdf6d4b29e4cfd8678991fe65c2efd5fdd143557186f0868"
+ "sha256:057d1966962fb86b3dc447bfac2c8e25ceed774509e49b180926a13a99910318",
+ "sha256:0b234c4a9519fac18d00b3c542f5b429513ea69372d4f95fbd0f716f5e2a89b5"
],
"index": "pypi",
- "version": "==8.12.56"
+ "version": "==8.12.57"
},
"pillow": {
"hashes": [
@@ -856,20 +802,20 @@
},
"psycopg2": {
"hashes": [
- "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c",
- "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf",
- "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362",
- "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7",
- "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461",
- "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126",
- "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981",
- "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56",
- "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305",
- "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2",
- "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"
+ "sha256:07b90a24d5056687781ddaef0ea172fd951f2f7293f6ffdd03d4f5077801f426",
+ "sha256:1da77c061bdaab450581458932ae5e469cc6e36e0d62f988376e9f513f11cb5c",
+ "sha256:46361c054df612c3cc813fdb343733d56543fb93565cff0f8ace422e4da06acb",
+ "sha256:839f9ea8f6098e39966d97fcb8d08548fbc57c523a1e27a1f0609addf40f777c",
+ "sha256:849bd868ae3369932127f0771c08d1109b254f08d48dc42493c3d1b87cb2d308",
+ "sha256:8de6a9fc5f42fa52f559e65120dcd7502394692490c98fed1221acf0819d7797",
+ "sha256:a11946bad3557ca254f17357d5a4ed63bdca45163e7a7d2bfb8e695df069cc3a",
+ "sha256:aa184d551a767ad25df3b8d22a0a62ef2962e0e374c04f6cbd1204947f540d61",
+ "sha256:aafa96f2da0071d6dd0cbb7633406d99f414b40ab0f918c9d9af7df928a1accb",
+ "sha256:c7fa041b4acb913f6968fce10169105af5200f296028251d817ab37847c30184",
+ "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"
],
"index": "pypi",
- "version": "==2.9.3"
+ "version": "==2.9.4"
},
"pyasn1": {
"hashes": [
@@ -964,10 +910,10 @@
},
"pytz": {
"hashes": [
- "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197",
- "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"
+ "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91",
+ "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"
],
- "version": "==2022.2.1"
+ "version": "==2022.4"
},
"pyyaml": {
"hashes": [
@@ -1056,11 +1002,11 @@
},
"sentry-sdk": {
"hashes": [
- "sha256:d6c71d2f85710b66822adaa954af7912bab135d6c85febd5b0f3dfd4ab37e181",
- "sha256:ef925b5338625448645a778428d8f22a3d17de8b28cc8e6fba60b93393ad86fe"
+ "sha256:2469240f6190aaebcb453033519eae69cfe8cc602065b4667e18ee14fc1e35dc",
+ "sha256:4fbace9a763285b608c06f01a807b51acb35f6059da6a01236654e08b0ee81ff"
],
"index": "pypi",
- "version": "==1.9.9"
+ "version": "==1.9.10"
},
"service-identity": {
"hashes": [
@@ -1071,11 +1017,11 @@
},
"setuptools": {
"hashes": [
- "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012",
- "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"
+ "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17",
+ "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"
],
"markers": "python_version >= '3.7'",
- "version": "==65.4.1"
+ "version": "==65.5.0"
},
"six": {
"hashes": [
@@ -1170,11 +1116,11 @@
},
"typing-extensions": {
"hashes": [
- "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
- "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
+ "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa",
+ "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"
],
"markers": "python_version >= '3.7'",
- "version": "==4.3.0"
+ "version": "==4.4.0"
},
"unittest-xml-reporting": {
"hashes": [
@@ -1433,60 +1379,46 @@
},
"zope.interface": {
"hashes": [
- "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192",
- "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702",
- "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09",
- "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4",
- "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a",
- "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3",
- "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf",
- "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c",
- "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d",
- "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78",
- "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83",
- "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531",
- "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46",
- "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021",
- "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94",
- "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc",
- "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63",
- "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54",
- "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117",
- "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25",
- "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05",
- "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e",
- "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1",
- "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004",
- "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2",
- "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e",
- "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f",
- "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f",
- "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120",
- "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f",
- "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1",
- "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9",
- "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e",
- "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7",
- "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8",
- "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b",
- "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155",
- "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7",
- "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c",
- "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325",
- "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d",
- "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb",
- "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e",
- "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959",
- "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7",
- "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920",
- "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e",
- "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48",
- "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8",
- "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
- "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
+ "sha256:006f8dd81fae28027fc28ada214855166712bf4f0bfbc5a8788f9b70982b9437",
+ "sha256:03f5ae315db0d0de668125d983e2a819a554f3fdb2d53b7e934e3eb3c3c7375d",
+ "sha256:0eb2b3e84f48dd9cfc8621c80fba905d7e228615c67f76c7df7c716065669bb6",
+ "sha256:1e3495bb0cdcea212154e558082c256f11b18031f05193ae2fb85d048848db14",
+ "sha256:26c1456520fdcafecc5765bec4783eeafd2e893eabc636908f50ee31fe5c738c",
+ "sha256:2cb3003941f5f4fa577479ac6d5db2b940acb600096dd9ea9bf07007f5cab46f",
+ "sha256:37ec9ade9902f412cc7e7a32d71f79dec3035bad9bd0170226252eed88763c48",
+ "sha256:3eedf3d04179774d750e8bb4463e6da350956a50ed44d7b86098e452d7ec385e",
+ "sha256:3f68404edb1a4fb6aa8a94675521ca26c83ebbdbb90e894f749ae0dc4ca98418",
+ "sha256:423c074e404f13e6fa07f4454f47fdbb38d358be22945bc812b94289d9142374",
+ "sha256:43490ad65d4c64e45a30e51a2beb7a6b63e1ff395302ad22392224eb618476d6",
+ "sha256:47ff078734a1030c48103422a99e71a7662d20258c00306546441adf689416f7",
+ "sha256:58a66c2020a347973168a4a9d64317bac52f9fdfd3e6b80b252be30da881a64e",
+ "sha256:58a975f89e4584d0223ab813c5ba4787064c68feef4b30d600f5e01de90ae9ce",
+ "sha256:5c6023ae7defd052cf76986ce77922177b0c2f3913bea31b5b28fbdf6cb7099e",
+ "sha256:6566b3d2657e7609cd8751bcb1eab1202b1692a7af223035a5887d64bb3a2f3b",
+ "sha256:687cab7f9ae18d2c146f315d0ca81e5ffe89a139b88277afa70d52f632515854",
+ "sha256:700ebf9662cf8df70e2f0cb4988e078c53f65ee3eefd5c9d80cf988c4175c8e3",
+ "sha256:740f3c1b44380658777669bcc42f650f5348e53797f2cee0d93dc9b0f9d7cc69",
+ "sha256:7bdcec93f152e0e1942102537eed7b166d6661ae57835b20a52a2a3d6a3e1bf3",
+ "sha256:7d9ec1e6694af39b687045712a8ad14ddcb568670d5eb1b66b48b98b9312afba",
+ "sha256:85dd6dd9aaae7a176948d8bb62e20e2968588fd787c29c5d0d964ab475168d3d",
+ "sha256:8b9f153208d74ccfa25449a0c6cb756ab792ce0dc99d9d771d935f039b38740c",
+ "sha256:8c791f4c203ccdbcda588ea4c8a6e4353e10435ea48ddd3d8734a26fe9714cba",
+ "sha256:970661ece2029915b8f7f70892e88404340fbdefd64728380cad41c8dce14ff4",
+ "sha256:9cdc4e898d3b1547d018829fd4a9f403e52e51bba24be0fbfa37f3174e1ef797",
+ "sha256:9dc4493aa3d87591e3d2bf1453e25b98038c839ca8e499df3d7106631b66fe83",
+ "sha256:a69c28d85bb7cf557751a5214cb3f657b2b035c8c96d71080c1253b75b79b69b",
+ "sha256:aeac590cce44e68ee8ad0b8ecf4d7bf15801f102d564ca1b0eb1f12f584ee656",
+ "sha256:be11fce0e6af6c0e8d93c10ef17b25aa7c4acb7ec644bff2596c0d639c49e20f",
+ "sha256:cbbf83914b9a883ab324f728de869f4e406e0cbcd92df7e0a88decf6f9ab7d5a",
+ "sha256:cfa614d049667bed1c737435c609c0956c5dc0dbafdc1145ee7935e4658582cb",
+ "sha256:d18fb0f6c8169d26044128a2e7d3c39377a8a151c564e87b875d379dbafd3930",
+ "sha256:d80f6236b57a95eb19d5e47eb68d0296119e1eff6deaa2971ab8abe3af918420",
+ "sha256:da7912ae76e1df6a1fb841b619110b1be4c86dfb36699d7fd2f177105cdea885",
+ "sha256:df6593e150d13cfcce69b0aec5df7bc248cb91e4258a7374c129bb6d56b4e5ca",
+ "sha256:f70726b60009433111fe9928f5d89cbb18962411d33c45fb19eb81b9bbd26fcd"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==5.4.0"
+ "version": "==5.5.0"
}
},
"develop": {
@@ -1534,16 +1466,16 @@
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
- "markers": "python_full_version >= '3.6.0'",
+ "markers": "python_version >= '3.6'",
"version": "==2.1.1"
},
"click": {
"hashes": [
- "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1",
- "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"
+ "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
+ "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
- "index": "pypi",
- "version": "==8.0.4"
+ "markers": "python_version >= '3.7'",
+ "version": "==8.1.3"
},
"codecov": {
"hashes": [
@@ -1612,11 +1544,11 @@
},
"django": {
"hashes": [
- "sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713",
- "sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b"
+ "sha256:26dc24f99c8956374a054bcbf58aab8dc0cad2e6ac82b0fe036b752c00eee793",
+ "sha256:b8d843714810ab88d59344507d4447be8b2cf12a49031363b6eed9f1b9b2280f"
],
"index": "pypi",
- "version": "==3.2.15"
+ "version": "==4.1.2"
},
"django-debug-toolbar": {
"hashes": [
@@ -1644,11 +1576,11 @@
},
"flake8-isort": {
"hashes": [
- "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0",
- "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"
+ "sha256:c73f9cbd1bf209887f602a27b827164ccfeba1676801b2aa23cb49051a1be79c",
+ "sha256:e336f928c7edc509684930ab124414194b7f4e237c712af8fcbdf49d8747b10c"
],
"index": "pypi",
- "version": "==4.2.0"
+ "version": "==5.0.0"
},
"flake8-quotes": {
"hashes": [
@@ -1781,13 +1713,6 @@
"markers": "python_version >= '3.6'",
"version": "==2.5.0"
},
- "pytz": {
- "hashes": [
- "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197",
- "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"
- ],
- "version": "==2022.2.1"
- },
"regex": {
"hashes": [
"sha256:003a2e1449d425afc817b5f0b3d4c4aa9072dd5f3dfbf6c7631b8dc7b13233de",
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index e525b32f5..a20f0491f 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -2416,6 +2416,8 @@ def tickets(self, request, *args, **kwargs):
tickets = Ticket.objects.filter(event=event)
types = tickets.values_list("type", flat=True).distinct()
+ # TODO: convert this into SQL
+
totals = []
available = []
From 1b0bda842025c5a52a72ee167dc162db635a096a Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sun, 16 Oct 2022 13:43:04 -0400
Subject: [PATCH 16/64] use master pipfile.lock
---
backend/Pipfile.lock | 305 +++++++++++++++++++++++++++----------------
1 file changed, 190 insertions(+), 115 deletions(-)
diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock
index bd497c315..6c195adb4 100644
--- a/backend/Pipfile.lock
+++ b/backend/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "ebeb34dcfd58ce86a7aee2ed18042c0fb124d534ee3b168f1943f1e294212b34"
+ "sha256": "1e5ed3497a97d54da1163f9def2f5aee2feaf2aa537a361d42376f30cb8cac4f"
},
"pipfile-spec": 6,
"requires": {
@@ -16,6 +16,13 @@
]
},
"default": {
+ "aioredis": {
+ "hashes": [
+ "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a",
+ "sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3"
+ ],
+ "version": "==1.3.1"
+ },
"anyio": {
"hashes": [
"sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b",
@@ -26,11 +33,11 @@
},
"arrow": {
"hashes": [
- "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1",
- "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"
+ "sha256:4bfacea734ead51495dc47df00421ecfd4ca1f2c0fbe58b9a26eaeddedc31caf",
+ "sha256:67f8be7c0cf420424bc62d8d7dc40b44e4bb2f7b515f9cc2954fb36e35797656"
],
- "markers": "python_version >= '3.6'",
- "version": "==1.2.3"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==0.14.7"
},
"asgiref": {
"hashes": [
@@ -75,32 +82,32 @@
"sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
"sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
],
- "markers": "python_version >= '3.6'",
+ "markers": "python_full_version >= '3.6.0'",
"version": "==4.11.1"
},
"bleach": {
"hashes": [
- "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a",
- "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"
+ "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da",
+ "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"
],
"index": "pypi",
- "version": "==5.0.1"
+ "version": "==4.1.0"
},
"boto3": {
"hashes": [
- "sha256:3225366014949039e6687387242e73f237f0fee0a9b7c20461894f1ee40686b8",
- "sha256:b295640bc1be637f8f7c8c8fca70781048d6397196109e59f20541824fab4b67"
+ "sha256:6194763348545bb1669ce8d03ba104be1ba822daa184613aa10b9303a6a79017",
+ "sha256:be151711bbb4db53e85dd5bbe506002ce6f2f21fc4e45fcf6d2cf356d32cc4c6"
],
"index": "pypi",
- "version": "==1.24.91"
+ "version": "==1.24.84"
},
"botocore": {
"hashes": [
- "sha256:1d6e97bd8653f732c7078b34aa2bb438e750898957e5a0a74b6c72918bc1d0f7",
- "sha256:c8fac203a391cc2e4b682877bfce70e723e33c529b35b399a1d574605fbeb1af"
+ "sha256:11f05d2acdf9a5f722856704b7b951b180647fb4340e1b5048b27273dc323909",
+ "sha256:da15026329706caf83323d84996f5ff5c527837347633fca9b3b1be0efa60841"
],
"markers": "python_version >= '3.7'",
- "version": "==1.27.91"
+ "version": "==1.27.84"
},
"bs4": {
"hashes": [
@@ -204,27 +211,27 @@
},
"channels-redis": {
"hashes": [
- "sha256:122414f29f525f7b9e0c9d59cdcfc4dc1b0eecba16fbb6a1c23f1d9b58f49dcb",
- "sha256:81b59d68f53313e1aa891f23591841b684abb936b42e4d1a966d9e4dc63a95ec"
+ "sha256:78e4a2f2b2a744fe5a87848ec36b5ee49f522c6808cefe6c583663d0d531faa8",
+ "sha256:ba7e2ad170f273c372812dd32aaac102d68d4e508172abb1cfda3160b7333890"
],
"index": "pypi",
- "version": "==4.0.0"
+ "version": "==3.4.1"
},
"charset-normalizer": {
"hashes": [
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
- "markers": "python_version >= '3.6'",
+ "markers": "python_full_version >= '3.6.0'",
"version": "==2.1.1"
},
"click": {
"hashes": [
- "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
- "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
+ "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1",
+ "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"
],
- "markers": "python_version >= '3.7'",
- "version": "==8.1.3"
+ "index": "pypi",
+ "version": "==8.0.4"
},
"conditional": {
"hashes": [
@@ -313,11 +320,11 @@
},
"django": {
"hashes": [
- "sha256:26dc24f99c8956374a054bcbf58aab8dc0cad2e6ac82b0fe036b752c00eee793",
- "sha256:b8d843714810ab88d59344507d4447be8b2cf12a49031363b6eed9f1b9b2280f"
+ "sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713",
+ "sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b"
],
"index": "pypi",
- "version": "==4.1.2"
+ "version": "==3.2.15"
},
"django-clone": {
"hashes": [
@@ -445,6 +452,53 @@
"markers": "python_version >= '3.7'",
"version": "==0.14.0"
},
+ "hiredis": {
+ "hashes": [
+ "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e",
+ "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27",
+ "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163",
+ "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc",
+ "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26",
+ "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e",
+ "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579",
+ "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a",
+ "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048",
+ "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87",
+ "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63",
+ "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54",
+ "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05",
+ "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb",
+ "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea",
+ "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5",
+ "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e",
+ "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc",
+ "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99",
+ "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a",
+ "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581",
+ "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426",
+ "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db",
+ "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a",
+ "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a",
+ "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d",
+ "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443",
+ "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79",
+ "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d",
+ "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9",
+ "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d",
+ "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485",
+ "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5",
+ "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048",
+ "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0",
+ "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6",
+ "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41",
+ "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298",
+ "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce",
+ "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
+ "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2.0.0"
+ },
"httptools": {
"hashes": [
"sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9",
@@ -501,20 +555,20 @@
},
"ics": {
"hashes": [
- "sha256:5fcf4d29ec6e7dfcb84120abd617bbba632eb77b097722b7df70e48dbcf26103",
- "sha256:6743539bca10391635249b87d74fcd1094af20b82098bebf7c7521df91209f05",
- "sha256:da352bdf8418619dc93611e6d251f3cefbb42664777f6e00b580a722098124b7"
+ "sha256:3b606205b9582ad27dff77f9b227a30d02fdac532731927fe39df1f1ddf8673f",
+ "sha256:81113a2bb3166c1afcd71cd450c968d40efc385601e9d8344733e00ad8f53429",
+ "sha256:bf5fbdef6e1e073afdadf1b996f0271186dd114a148e38e795919a1ae644d6ac"
],
"index": "pypi",
- "version": "==0.7.2"
+ "version": "==0.7"
},
"identify": {
"hashes": [
- "sha256:6c32dbd747aa4ceee1df33f25fed0b0f6e0d65721b15bd151307ff7056d50245",
- "sha256:b276db7ec52d7e89f5bc4653380e33054ddc803d25875952ad90b0f012cbcdaa"
+ "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6",
+ "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97"
],
"markers": "python_version >= '3.7'",
- "version": "==2.5.6"
+ "version": "==2.5.5"
},
"idna": {
"hashes": [
@@ -526,10 +580,10 @@
},
"incremental": {
"hashes": [
- "sha256:912feeb5e0f7e0188e6f42241d2f450002e11bbc0937c65865045854c24c0bd0",
- "sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51"
+ "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57",
+ "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"
],
- "version": "==22.10.0"
+ "version": "==21.3.0"
},
"jmespath": {
"hashes": [
@@ -541,11 +595,11 @@
},
"jsonref": {
"hashes": [
- "sha256:68b330c6815dc0d490dbb3d65ccda265ddde9f7856fd2f3322f971d456ea7549",
- "sha256:9480ad1b500f7e795daeb0ef29f9c55ae3a9ab38fb8d6659b6f4868acb5a5bc8"
+ "sha256:b1e82fa0b62e2c2796a13e5401fe51790b248f6d9bf9d7212a3e31a3501b291f",
+ "sha256:f3c45b121cf6257eafabdc3a8008763aed1cd7da06dbabc59a9e4d2a5e4e6697"
],
"index": "pypi",
- "version": "==0.3.0"
+ "version": "==0.2"
},
"lxml": {
"hashes": [
@@ -714,11 +768,11 @@
},
"phonenumbers": {
"hashes": [
- "sha256:057d1966962fb86b3dc447bfac2c8e25ceed774509e49b180926a13a99910318",
- "sha256:0b234c4a9519fac18d00b3c542f5b429513ea69372d4f95fbd0f716f5e2a89b5"
+ "sha256:80a7422cf0999a6f9b7a2e6cfbdbbfcc56ab5b75414dc3b805bbec91276b64a3",
+ "sha256:82a4f226c930d02dcdf6d4b29e4cfd8678991fe65c2efd5fdd143557186f0868"
],
"index": "pypi",
- "version": "==8.12.57"
+ "version": "==8.12.56"
},
"pillow": {
"hashes": [
@@ -802,20 +856,20 @@
},
"psycopg2": {
"hashes": [
- "sha256:07b90a24d5056687781ddaef0ea172fd951f2f7293f6ffdd03d4f5077801f426",
- "sha256:1da77c061bdaab450581458932ae5e469cc6e36e0d62f988376e9f513f11cb5c",
- "sha256:46361c054df612c3cc813fdb343733d56543fb93565cff0f8ace422e4da06acb",
- "sha256:839f9ea8f6098e39966d97fcb8d08548fbc57c523a1e27a1f0609addf40f777c",
- "sha256:849bd868ae3369932127f0771c08d1109b254f08d48dc42493c3d1b87cb2d308",
- "sha256:8de6a9fc5f42fa52f559e65120dcd7502394692490c98fed1221acf0819d7797",
- "sha256:a11946bad3557ca254f17357d5a4ed63bdca45163e7a7d2bfb8e695df069cc3a",
- "sha256:aa184d551a767ad25df3b8d22a0a62ef2962e0e374c04f6cbd1204947f540d61",
- "sha256:aafa96f2da0071d6dd0cbb7633406d99f414b40ab0f918c9d9af7df928a1accb",
- "sha256:c7fa041b4acb913f6968fce10169105af5200f296028251d817ab37847c30184",
- "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f"
+ "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c",
+ "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf",
+ "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362",
+ "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7",
+ "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461",
+ "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126",
+ "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981",
+ "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56",
+ "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305",
+ "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2",
+ "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"
],
"index": "pypi",
- "version": "==2.9.4"
+ "version": "==2.9.3"
},
"pyasn1": {
"hashes": [
@@ -910,10 +964,10 @@
},
"pytz": {
"hashes": [
- "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91",
- "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"
+ "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197",
+ "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"
],
- "version": "==2022.4"
+ "version": "==2022.2.1"
},
"pyyaml": {
"hashes": [
@@ -1002,11 +1056,11 @@
},
"sentry-sdk": {
"hashes": [
- "sha256:2469240f6190aaebcb453033519eae69cfe8cc602065b4667e18ee14fc1e35dc",
- "sha256:4fbace9a763285b608c06f01a807b51acb35f6059da6a01236654e08b0ee81ff"
+ "sha256:d6c71d2f85710b66822adaa954af7912bab135d6c85febd5b0f3dfd4ab37e181",
+ "sha256:ef925b5338625448645a778428d8f22a3d17de8b28cc8e6fba60b93393ad86fe"
],
"index": "pypi",
- "version": "==1.9.10"
+ "version": "==1.9.9"
},
"service-identity": {
"hashes": [
@@ -1017,11 +1071,11 @@
},
"setuptools": {
"hashes": [
- "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17",
- "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"
+ "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012",
+ "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"
],
"markers": "python_version >= '3.7'",
- "version": "==65.5.0"
+ "version": "==65.4.1"
},
"six": {
"hashes": [
@@ -1116,11 +1170,11 @@
},
"typing-extensions": {
"hashes": [
- "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa",
- "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"
+ "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
+ "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
],
"markers": "python_version >= '3.7'",
- "version": "==4.4.0"
+ "version": "==4.3.0"
},
"unittest-xml-reporting": {
"hashes": [
@@ -1379,46 +1433,60 @@
},
"zope.interface": {
"hashes": [
- "sha256:006f8dd81fae28027fc28ada214855166712bf4f0bfbc5a8788f9b70982b9437",
- "sha256:03f5ae315db0d0de668125d983e2a819a554f3fdb2d53b7e934e3eb3c3c7375d",
- "sha256:0eb2b3e84f48dd9cfc8621c80fba905d7e228615c67f76c7df7c716065669bb6",
- "sha256:1e3495bb0cdcea212154e558082c256f11b18031f05193ae2fb85d048848db14",
- "sha256:26c1456520fdcafecc5765bec4783eeafd2e893eabc636908f50ee31fe5c738c",
- "sha256:2cb3003941f5f4fa577479ac6d5db2b940acb600096dd9ea9bf07007f5cab46f",
- "sha256:37ec9ade9902f412cc7e7a32d71f79dec3035bad9bd0170226252eed88763c48",
- "sha256:3eedf3d04179774d750e8bb4463e6da350956a50ed44d7b86098e452d7ec385e",
- "sha256:3f68404edb1a4fb6aa8a94675521ca26c83ebbdbb90e894f749ae0dc4ca98418",
- "sha256:423c074e404f13e6fa07f4454f47fdbb38d358be22945bc812b94289d9142374",
- "sha256:43490ad65d4c64e45a30e51a2beb7a6b63e1ff395302ad22392224eb618476d6",
- "sha256:47ff078734a1030c48103422a99e71a7662d20258c00306546441adf689416f7",
- "sha256:58a66c2020a347973168a4a9d64317bac52f9fdfd3e6b80b252be30da881a64e",
- "sha256:58a975f89e4584d0223ab813c5ba4787064c68feef4b30d600f5e01de90ae9ce",
- "sha256:5c6023ae7defd052cf76986ce77922177b0c2f3913bea31b5b28fbdf6cb7099e",
- "sha256:6566b3d2657e7609cd8751bcb1eab1202b1692a7af223035a5887d64bb3a2f3b",
- "sha256:687cab7f9ae18d2c146f315d0ca81e5ffe89a139b88277afa70d52f632515854",
- "sha256:700ebf9662cf8df70e2f0cb4988e078c53f65ee3eefd5c9d80cf988c4175c8e3",
- "sha256:740f3c1b44380658777669bcc42f650f5348e53797f2cee0d93dc9b0f9d7cc69",
- "sha256:7bdcec93f152e0e1942102537eed7b166d6661ae57835b20a52a2a3d6a3e1bf3",
- "sha256:7d9ec1e6694af39b687045712a8ad14ddcb568670d5eb1b66b48b98b9312afba",
- "sha256:85dd6dd9aaae7a176948d8bb62e20e2968588fd787c29c5d0d964ab475168d3d",
- "sha256:8b9f153208d74ccfa25449a0c6cb756ab792ce0dc99d9d771d935f039b38740c",
- "sha256:8c791f4c203ccdbcda588ea4c8a6e4353e10435ea48ddd3d8734a26fe9714cba",
- "sha256:970661ece2029915b8f7f70892e88404340fbdefd64728380cad41c8dce14ff4",
- "sha256:9cdc4e898d3b1547d018829fd4a9f403e52e51bba24be0fbfa37f3174e1ef797",
- "sha256:9dc4493aa3d87591e3d2bf1453e25b98038c839ca8e499df3d7106631b66fe83",
- "sha256:a69c28d85bb7cf557751a5214cb3f657b2b035c8c96d71080c1253b75b79b69b",
- "sha256:aeac590cce44e68ee8ad0b8ecf4d7bf15801f102d564ca1b0eb1f12f584ee656",
- "sha256:be11fce0e6af6c0e8d93c10ef17b25aa7c4acb7ec644bff2596c0d639c49e20f",
- "sha256:cbbf83914b9a883ab324f728de869f4e406e0cbcd92df7e0a88decf6f9ab7d5a",
- "sha256:cfa614d049667bed1c737435c609c0956c5dc0dbafdc1145ee7935e4658582cb",
- "sha256:d18fb0f6c8169d26044128a2e7d3c39377a8a151c564e87b875d379dbafd3930",
- "sha256:d80f6236b57a95eb19d5e47eb68d0296119e1eff6deaa2971ab8abe3af918420",
- "sha256:da7912ae76e1df6a1fb841b619110b1be4c86dfb36699d7fd2f177105cdea885",
- "sha256:df6593e150d13cfcce69b0aec5df7bc248cb91e4258a7374c129bb6d56b4e5ca",
- "sha256:f70726b60009433111fe9928f5d89cbb18962411d33c45fb19eb81b9bbd26fcd"
+ "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192",
+ "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702",
+ "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09",
+ "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4",
+ "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a",
+ "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3",
+ "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf",
+ "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c",
+ "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d",
+ "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78",
+ "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83",
+ "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531",
+ "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46",
+ "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021",
+ "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94",
+ "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc",
+ "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63",
+ "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54",
+ "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117",
+ "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25",
+ "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05",
+ "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e",
+ "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1",
+ "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004",
+ "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2",
+ "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e",
+ "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f",
+ "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f",
+ "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120",
+ "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f",
+ "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1",
+ "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9",
+ "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e",
+ "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7",
+ "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8",
+ "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b",
+ "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155",
+ "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7",
+ "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c",
+ "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325",
+ "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d",
+ "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb",
+ "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e",
+ "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959",
+ "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7",
+ "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920",
+ "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e",
+ "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48",
+ "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8",
+ "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
+ "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==5.5.0"
+ "version": "==5.4.0"
}
},
"develop": {
@@ -1466,16 +1534,16 @@
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
- "markers": "python_version >= '3.6'",
+ "markers": "python_full_version >= '3.6.0'",
"version": "==2.1.1"
},
"click": {
"hashes": [
- "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
- "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
+ "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1",
+ "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"
],
- "markers": "python_version >= '3.7'",
- "version": "==8.1.3"
+ "index": "pypi",
+ "version": "==8.0.4"
},
"codecov": {
"hashes": [
@@ -1544,11 +1612,11 @@
},
"django": {
"hashes": [
- "sha256:26dc24f99c8956374a054bcbf58aab8dc0cad2e6ac82b0fe036b752c00eee793",
- "sha256:b8d843714810ab88d59344507d4447be8b2cf12a49031363b6eed9f1b9b2280f"
+ "sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713",
+ "sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b"
],
"index": "pypi",
- "version": "==4.1.2"
+ "version": "==3.2.15"
},
"django-debug-toolbar": {
"hashes": [
@@ -1576,11 +1644,11 @@
},
"flake8-isort": {
"hashes": [
- "sha256:c73f9cbd1bf209887f602a27b827164ccfeba1676801b2aa23cb49051a1be79c",
- "sha256:e336f928c7edc509684930ab124414194b7f4e237c712af8fcbdf49d8747b10c"
+ "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0",
+ "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"
],
"index": "pypi",
- "version": "==5.0.0"
+ "version": "==4.2.0"
},
"flake8-quotes": {
"hashes": [
@@ -1713,6 +1781,13 @@
"markers": "python_version >= '3.6'",
"version": "==2.5.0"
},
+ "pytz": {
+ "hashes": [
+ "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197",
+ "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"
+ ],
+ "version": "==2022.2.1"
+ },
"regex": {
"hashes": [
"sha256:003a2e1449d425afc817b5f0b3d4c4aa9072dd5f3dfbf6c7631b8dc7b13233de",
From 094ad428d3469b94456b733d12222ff9012ad0c9 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sun, 16 Oct 2022 13:50:17 -0400
Subject: [PATCH 17/64] lint and pre-commit fixes
---
.pre-commit-config.yaml | 2 +-
backend/clubs/models.py | 8 ++------
backend/clubs/serializers.py | 10 +++-------
backend/clubs/views.py | 27 +++++++--------------------
4 files changed, 13 insertions(+), 34 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 72e754069..9f5abe424 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,7 +4,7 @@ repos:
hooks:
- id: black
name: black
- entry: black
+ entry: cd backend && pipenv run black
language: python
types: [python]
require_serial: true
diff --git a/backend/clubs/models.py b/backend/clubs/models.py
index a9f90de55..4e7026497 100644
--- a/backend/clubs/models.py
+++ b/backend/clubs/models.py
@@ -1574,9 +1574,7 @@ class ApplicationCommittee(models.Model):
name = models.TextField(blank=True)
application = models.ForeignKey(
- ClubApplication,
- related_name="committees",
- on_delete=models.CASCADE,
+ ClubApplication, related_name="committees", on_delete=models.CASCADE,
)
def get_word_limit(self):
@@ -1628,9 +1626,7 @@ class ApplicationMultipleChoice(models.Model):
value = models.TextField(blank=True)
question = models.ForeignKey(
- ApplicationQuestion,
- related_name="multiple_choice",
- on_delete=models.CASCADE,
+ ApplicationQuestion, related_name="multiple_choice", on_delete=models.CASCADE,
)
diff --git a/backend/clubs/serializers.py b/backend/clubs/serializers.py
index 9e3286308..c6f0f62de 100644
--- a/backend/clubs/serializers.py
+++ b/backend/clubs/serializers.py
@@ -2012,9 +2012,7 @@ def get_clubs(self, obj):
# hide non public memberships if not superuser
if user is None or not user.has_perm("clubs.manage_club"):
queryset = queryset.filter(
- membership__person=obj,
- membership__public=True,
- approved=True,
+ membership__person=obj, membership__public=True, approved=True,
)
serializer = MembershipClubListSerializer(
@@ -2415,8 +2413,7 @@ def save(self):
ApplicationMultipleChoice.objects.filter(question=question_obj).delete()
for choice in multiple_choice:
ApplicationMultipleChoice.objects.create(
- value=choice["value"],
- question=question_obj,
+ value=choice["value"], question=question_obj,
)
# manually create committee choices as Django does not
@@ -2705,8 +2702,7 @@ def save(self):
for name in committees:
if name not in prev_committee_names:
ApplicationCommittee.objects.create(
- name=name,
- application=application_obj,
+ name=name, application=application_obj,
)
return application_obj
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index a20f0491f..2a27f7c2d 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -1080,8 +1080,7 @@ def get_queryset(self):
self.request.user, get_user_model()
):
SearchQuery(
- person=self.request.user,
- query=self.request.query_params.get("search"),
+ person=self.request.user, query=self.request.query_params.get("search"),
).save()
# select subset of clubs if requested
@@ -1673,9 +1672,7 @@ def constitutions(self, request, *args, **kwargs):
query = (
Club.objects.filter(badges=badge, archived=False)
.order_by(Lower("name"))
- .prefetch_related(
- Prefetch("asset_set", to_attr="prefetch_asset_set"),
- )
+ .prefetch_related(Prefetch("asset_set", to_attr="prefetch_asset_set"),)
)
if request.user.is_authenticated:
query = query.prefetch_related(
@@ -3222,9 +3219,7 @@ def destroy(self, request, *args, **kwargs):
def get_queryset(self):
return MembershipRequest.objects.filter(
- person=self.request.user,
- withdrew=False,
- club__archived=False,
+ person=self.request.user, withdrew=False, club__archived=False,
)
@@ -4348,9 +4343,7 @@ def get(self, request):
try:
response = zoom_api_call(
- request.user,
- "GET",
- "https://api.zoom.us/v2/users/{uid}/settings",
+ request.user, "GET", "https://api.zoom.us/v2/users/{uid}/settings",
)
except requests.exceptions.HTTPError as e:
raise DRFValidationError(
@@ -4471,9 +4464,7 @@ def get_operation_id(self, **kwargs):
def get_object(self):
user = self.request.user
prefetch_related_objects(
- [user],
- "profile__school",
- "profile__major",
+ [user], "profile__school", "profile__major",
)
return user
@@ -4906,9 +4897,7 @@ def question_response(self, *args, **kwargs):
}
)
submission = ApplicationSubmission.objects.create(
- user=self.request.user,
- application=application,
- committee=committee,
+ user=self.request.user, application=application, committee=committee,
)
for question_pk in questions:
question = ApplicationQuestion.objects.filter(pk=question_pk).first()
@@ -4929,9 +4918,7 @@ def question_response(self, *args, **kwargs):
text = question_data.get("text", None)
if text is not None and text != "":
obj = ApplicationQuestionResponse.objects.create(
- text=text,
- question=question,
- submission=submission,
+ text=text, question=question, submission=submission,
).save()
response = Response(ApplicationQuestionResponseSerializer(obj).data)
elif question_type == ApplicationQuestion.MULTIPLE_CHOICE:
From d67a3a67bd8638c89aee0431d9a3c455a8b8ffd7 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sun, 16 Oct 2022 19:20:17 -0400
Subject: [PATCH 18/64] minor bugs
---
backend/clubs/serializers.py | 12 ++++++++----
backend/clubs/views.py | 29 +++++++++++++++++++++--------
2 files changed, 29 insertions(+), 12 deletions(-)
diff --git a/backend/clubs/serializers.py b/backend/clubs/serializers.py
index c6f0f62de..1f803a32a 100644
--- a/backend/clubs/serializers.py
+++ b/backend/clubs/serializers.py
@@ -344,7 +344,7 @@ class ClubEventSerializer(serializers.ModelSerializer):
creator = serializers.HiddenField(default=serializers.CurrentUserDefault())
def get_ticketed(self, obj) -> bool:
- return Event.tickets.exists()
+ return obj.tickets.count() > 0
def get_event_url(self, obj):
# if no url, return that
@@ -2012,7 +2012,9 @@ def get_clubs(self, obj):
# hide non public memberships if not superuser
if user is None or not user.has_perm("clubs.manage_club"):
queryset = queryset.filter(
- membership__person=obj, membership__public=True, approved=True,
+ membership__person=obj,
+ membership__public=True,
+ approved=True,
)
serializer = MembershipClubListSerializer(
@@ -2413,7 +2415,8 @@ def save(self):
ApplicationMultipleChoice.objects.filter(question=question_obj).delete()
for choice in multiple_choice:
ApplicationMultipleChoice.objects.create(
- value=choice["value"], question=question_obj,
+ value=choice["value"],
+ question=question_obj,
)
# manually create committee choices as Django does not
@@ -2702,7 +2705,8 @@ def save(self):
for name in committees:
if name not in prev_committee_names:
ApplicationCommittee.objects.create(
- name=name, application=application_obj,
+ name=name,
+ application=application_obj,
)
return application_obj
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 2a27f7c2d..6957425f1 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -1080,7 +1080,8 @@ def get_queryset(self):
self.request.user, get_user_model()
):
SearchQuery(
- person=self.request.user, query=self.request.query_params.get("search"),
+ person=self.request.user,
+ query=self.request.query_params.get("search"),
).save()
# select subset of clubs if requested
@@ -1672,7 +1673,9 @@ def constitutions(self, request, *args, **kwargs):
query = (
Club.objects.filter(badges=badge, archived=False)
.order_by(Lower("name"))
- .prefetch_related(Prefetch("asset_set", to_attr="prefetch_asset_set"),)
+ .prefetch_related(
+ Prefetch("asset_set", to_attr="prefetch_asset_set"),
+ )
)
if request.user.is_authenticated:
query = query.prefetch_related(
@@ -2371,7 +2374,7 @@ def buyers(self, request, *args, **kwargs):
)
buyers = tickets.filter(owner__isnull=False).values(
- "id", "fullname", "owner_id", "type"
+ "fullname", "id", "owner_id", "type"
)
return Response({"buyers": buyers})
@@ -3219,7 +3222,9 @@ def destroy(self, request, *args, **kwargs):
def get_queryset(self):
return MembershipRequest.objects.filter(
- person=self.request.user, withdrew=False, club__archived=False,
+ person=self.request.user,
+ withdrew=False,
+ club__archived=False,
)
@@ -4343,7 +4348,9 @@ def get(self, request):
try:
response = zoom_api_call(
- request.user, "GET", "https://api.zoom.us/v2/users/{uid}/settings",
+ request.user,
+ "GET",
+ "https://api.zoom.us/v2/users/{uid}/settings",
)
except requests.exceptions.HTTPError as e:
raise DRFValidationError(
@@ -4464,7 +4471,9 @@ def get_operation_id(self, **kwargs):
def get_object(self):
user = self.request.user
prefetch_related_objects(
- [user], "profile__school", "profile__major",
+ [user],
+ "profile__school",
+ "profile__major",
)
return user
@@ -4897,7 +4906,9 @@ def question_response(self, *args, **kwargs):
}
)
submission = ApplicationSubmission.objects.create(
- user=self.request.user, application=application, committee=committee,
+ user=self.request.user,
+ application=application,
+ committee=committee,
)
for question_pk in questions:
question = ApplicationQuestion.objects.filter(pk=question_pk).first()
@@ -4918,7 +4929,9 @@ def question_response(self, *args, **kwargs):
text = question_data.get("text", None)
if text is not None and text != "":
obj = ApplicationQuestionResponse.objects.create(
- text=text, question=question, submission=submission,
+ text=text,
+ question=question,
+ submission=submission,
).save()
response = Response(ApplicationQuestionResponseSerializer(obj).data)
elif question_type == ApplicationQuestion.MULTIPLE_CHOICE:
From a352b01d391a61f1eef4b7550dfc139b99a9a420 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sat, 12 Nov 2022 13:24:32 -0500
Subject: [PATCH 19/64] add to populate script
---
backend/clubs/management/commands/populate.py | 34 ++++++++++++++++++-
backend/clubs/views.py | 5 ++-
2 files changed, 37 insertions(+), 2 deletions(-)
diff --git a/backend/clubs/management/commands/populate.py b/backend/clubs/management/commands/populate.py
index 0ea8e7d63..5de5d06c5 100644
--- a/backend/clubs/management/commands/populate.py
+++ b/backend/clubs/management/commands/populate.py
@@ -15,6 +15,7 @@
ApplicationQuestion,
ApplicationSubmission,
Badge,
+ Cart,
Club,
ClubApplication,
ClubFair,
@@ -28,6 +29,7 @@
StudentType,
Tag,
Testimonial,
+ Ticket,
Year,
)
@@ -662,7 +664,10 @@ def get_image(url):
status_counter % len(ApplicationSubmission.STATUS_TYPES)
][0]
ApplicationSubmission.objects.create(
- status=status, user=user, application=application, committee=None,
+ status=status,
+ user=user,
+ application=application,
+ committee=None,
)
status_counter += 1
for committee in application.committees.all():
@@ -746,4 +751,31 @@ def get_image(url):
first_mship.save()
count += 1
+ # add tickets
+
+ hr = Club.objects.get(code="harvard-rejects")
+
+ hr_events = Event.objects.filter(club=hr)
+
+ for idx, e in enumerate(hr_events[:3]):
+ # Switch up person every so often
+ person = ben if idx < 2 else user_objs[1]
+
+ # Create some unowned tickets
+ Ticket.objects.bulk_create(
+ [Ticket(event=e, type="Regular") for _ in range(10)]
+ )
+
+ Ticket.objects.bulk_create(
+ [Ticket(event=e, type="Premium") for _ in range(5)]
+ )
+
+ # Create some owned tickets and tickets in cart
+ for i in range((idx + 1) * 10):
+ if i % 5:
+ Ticket.objects.create(event=e, owner=person, type="Regular")
+ else:
+ c, _ = Cart.objects.get_or_create(owner=person)
+ c.tickets.add(Ticket.objects.create(event=e, type="Premium"))
+
self.stdout.write("Finished populating database!")
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 6957425f1..3fd0e4241 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -2436,6 +2436,8 @@ def tickets(self, request, *args, **kwargs):
@transaction.atomic
def create_tickets(self, request, *args, **kwargs):
"""
+ Create ticket offerings for event
+ ---
requestBody:
content:
application/json:
@@ -2456,12 +2458,13 @@ def create_tickets(self, request, *args, **kwargs):
content:
application/json:
schema:
- type: object
properties:
detail:
type: string
+ ---
"""
event = self.get_object()
+
quantities = request.data.get("quantities")
# Atomicity ensures idempotency
From 7367db1c7393648d0288db9809d5fcb5bfa2939c Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sat, 12 Nov 2022 13:43:17 -0500
Subject: [PATCH 20/64] lint
---
backend/clubs/management/commands/populate.py | 7 ++---
backend/clubs/serializers.py | 10 +++----
backend/clubs/views.py | 27 +++++--------------
3 files changed, 12 insertions(+), 32 deletions(-)
diff --git a/backend/clubs/management/commands/populate.py b/backend/clubs/management/commands/populate.py
index 5de5d06c5..a499bacef 100644
--- a/backend/clubs/management/commands/populate.py
+++ b/backend/clubs/management/commands/populate.py
@@ -664,10 +664,7 @@ def get_image(url):
status_counter % len(ApplicationSubmission.STATUS_TYPES)
][0]
ApplicationSubmission.objects.create(
- status=status,
- user=user,
- application=application,
- committee=None,
+ status=status, user=user, application=application, committee=None,
)
status_counter += 1
for committee in application.committees.all():
@@ -751,7 +748,7 @@ def get_image(url):
first_mship.save()
count += 1
- # add tickets
+ # Add tickets
hr = Club.objects.get(code="harvard-rejects")
diff --git a/backend/clubs/serializers.py b/backend/clubs/serializers.py
index 1f803a32a..f44761caa 100644
--- a/backend/clubs/serializers.py
+++ b/backend/clubs/serializers.py
@@ -2012,9 +2012,7 @@ def get_clubs(self, obj):
# hide non public memberships if not superuser
if user is None or not user.has_perm("clubs.manage_club"):
queryset = queryset.filter(
- membership__person=obj,
- membership__public=True,
- approved=True,
+ membership__person=obj, membership__public=True, approved=True,
)
serializer = MembershipClubListSerializer(
@@ -2415,8 +2413,7 @@ def save(self):
ApplicationMultipleChoice.objects.filter(question=question_obj).delete()
for choice in multiple_choice:
ApplicationMultipleChoice.objects.create(
- value=choice["value"],
- question=question_obj,
+ value=choice["value"], question=question_obj,
)
# manually create committee choices as Django does not
@@ -2705,8 +2702,7 @@ def save(self):
for name in committees:
if name not in prev_committee_names:
ApplicationCommittee.objects.create(
- name=name,
- application=application_obj,
+ name=name, application=application_obj,
)
return application_obj
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 3fd0e4241..550fa3c6d 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -1080,8 +1080,7 @@ def get_queryset(self):
self.request.user, get_user_model()
):
SearchQuery(
- person=self.request.user,
- query=self.request.query_params.get("search"),
+ person=self.request.user, query=self.request.query_params.get("search"),
).save()
# select subset of clubs if requested
@@ -1673,9 +1672,7 @@ def constitutions(self, request, *args, **kwargs):
query = (
Club.objects.filter(badges=badge, archived=False)
.order_by(Lower("name"))
- .prefetch_related(
- Prefetch("asset_set", to_attr="prefetch_asset_set"),
- )
+ .prefetch_related(Prefetch("asset_set", to_attr="prefetch_asset_set"),)
)
if request.user.is_authenticated:
query = query.prefetch_related(
@@ -3225,9 +3222,7 @@ def destroy(self, request, *args, **kwargs):
def get_queryset(self):
return MembershipRequest.objects.filter(
- person=self.request.user,
- withdrew=False,
- club__archived=False,
+ person=self.request.user, withdrew=False, club__archived=False,
)
@@ -4351,9 +4346,7 @@ def get(self, request):
try:
response = zoom_api_call(
- request.user,
- "GET",
- "https://api.zoom.us/v2/users/{uid}/settings",
+ request.user, "GET", "https://api.zoom.us/v2/users/{uid}/settings",
)
except requests.exceptions.HTTPError as e:
raise DRFValidationError(
@@ -4474,9 +4467,7 @@ def get_operation_id(self, **kwargs):
def get_object(self):
user = self.request.user
prefetch_related_objects(
- [user],
- "profile__school",
- "profile__major",
+ [user], "profile__school", "profile__major",
)
return user
@@ -4909,9 +4900,7 @@ def question_response(self, *args, **kwargs):
}
)
submission = ApplicationSubmission.objects.create(
- user=self.request.user,
- application=application,
- committee=committee,
+ user=self.request.user, application=application, committee=committee,
)
for question_pk in questions:
question = ApplicationQuestion.objects.filter(pk=question_pk).first()
@@ -4932,9 +4921,7 @@ def question_response(self, *args, **kwargs):
text = question_data.get("text", None)
if text is not None and text != "":
obj = ApplicationQuestionResponse.objects.create(
- text=text,
- question=question,
- submission=submission,
+ text=text, question=question, submission=submission,
).save()
response = Response(ApplicationQuestionResponseSerializer(obj).data)
elif question_type == ApplicationQuestion.MULTIPLE_CHOICE:
From 7f650dc7d77c8118e5db9e3efccf40a47132f750 Mon Sep 17 00:00:00 2001
From: Rohan Gupta
Date: Sat, 12 Nov 2022 15:12:36 -0500
Subject: [PATCH 21/64] rebase, fix some documentation
---
backend/clubs/migrations/0091_cart_ticket.py | 2 +-
.../migrations/0092_auto_20220211_1732.py | 55 -------------------
backend/clubs/views.py | 30 ++++++----
backend/tests/clubs/test_documentation.py | 2 +-
4 files changed, 22 insertions(+), 67 deletions(-)
delete mode 100644 backend/clubs/migrations/0092_auto_20220211_1732.py
diff --git a/backend/clubs/migrations/0091_cart_ticket.py b/backend/clubs/migrations/0091_cart_ticket.py
index 758aa2e30..74ae8cdd9 100644
--- a/backend/clubs/migrations/0091_cart_ticket.py
+++ b/backend/clubs/migrations/0091_cart_ticket.py
@@ -1,4 +1,4 @@
-# Generated by Django 3.2.8 on 2022-10-02 16:50
+# Generated by Django 3.2.8 on 2022-11-12 20:05
import uuid
diff --git a/backend/clubs/migrations/0092_auto_20220211_1732.py b/backend/clubs/migrations/0092_auto_20220211_1732.py
deleted file mode 100644
index ee9adba59..000000000
--- a/backend/clubs/migrations/0092_auto_20220211_1732.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# Generated by Django 3.2.8 on 2022-02-11 22:32
-
-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),
- ("clubs", "0091_ticket"),
- ]
-
- operations = [
- migrations.AddField(
- model_name="ticket",
- name="held",
- field=models.BooleanField(default=False),
- ),
- migrations.AddField(
- model_name="ticket",
- name="holding_expiration",
- field=models.DateTimeField(blank=True, null=True),
- ),
- migrations.CreateModel(
- name="Cart",
- fields=[
- (
- "id",
- models.AutoField(
- auto_created=True,
- primary_key=True,
- serialize=False,
- verbose_name="ID",
- ),
- ),
- (
- "owner",
- models.ForeignKey(
- on_delete=django.db.models.deletion.CASCADE,
- related_name="cart",
- to=settings.AUTH_USER_MODEL,
- ),
- ),
- ],
- ),
- migrations.AddField(
- model_name="ticket",
- name="carts",
- field=models.ManyToManyField(
- blank=True, related_name="tickets", to="clubs.Cart"
- ),
- ),
- ]
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index 550fa3c6d..f8e3bcefd 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -27,9 +27,17 @@
from django.core.serializers.json import DjangoJSONEncoder
from django.core.validators import validate_email
from django.db import transaction
-from django.db.models import Count, DurationField, ExpressionWrapper, F, Prefetch, Q
+from django.db.models import (
+ Count,
+ DurationField,
+ ExpressionWrapper,
+ F,
+ Prefetch,
+ Q,
+ TextField,
+)
from django.db.models.expressions import RawSQL, Value
-from django.db.models.functions import Lower, Trunc
+from django.db.models.functions import SHA1, Lower, Trunc
from django.db.models.functions.text import Concat
from django.db.models.query import prefetch_related_objects
from django.http import HttpResponse
@@ -2263,6 +2271,7 @@ def add_to_cart(self, request, *args, **kwargs):
content:
application/json:
schema:
+ type: object
properties:
detail:
type: string
@@ -2270,6 +2279,7 @@ def add_to_cart(self, request, *args, **kwargs):
content:
application/json:
schema:
+ type: object
properties:
detail:
type: string
@@ -2319,6 +2329,7 @@ def remove_from_cart(self, request, *args, **kwargs):
content:
application/json:
schema:
+ type: object
properties:
detail:
type: string
@@ -2361,7 +2372,7 @@ def buyers(self, request, *args, **kwargs):
id:
type: string
owner_id:
- type: int
+ type: integer
type:
type: string
---
@@ -2455,6 +2466,7 @@ def create_tickets(self, request, *args, **kwargs):
content:
application/json:
schema:
+ type: object
properties:
detail:
type: string
@@ -2496,6 +2508,7 @@ def upload(self, request, *args, **kwargs):
description: Returned if the file was successfully uploaded.
content: &upload_resp
application/json:
+ schema:
type: object
properties:
detail:
@@ -4565,17 +4578,13 @@ def checkout(self, request, *args, **kwargs):
NOTE: this does NOT buy tickets, it simply initiates a checkout process
which includes a 10-minute ticket hold
---
- requestBody:
- content:
- application/json:
- schema:
- type: object
-
+ requestBody: {}
responses:
"200":
content:
application/json:
schema:
+ type: object
properties:
detail:
type: string
@@ -4612,10 +4621,11 @@ def checkout_success_callback(self, request, *args, **kwargs):
content:
application/json:
schema:
+ type: object
properties:
detail:
type: string
-
+ ---
"""
cart = get_object_or_404(Cart, owner=self.request.user)
diff --git a/backend/tests/clubs/test_documentation.py b/backend/tests/clubs/test_documentation.py
index 01865073a..67bfe8170 100644
--- a/backend/tests/clubs/test_documentation.py
+++ b/backend/tests/clubs/test_documentation.py
@@ -204,8 +204,8 @@ def test_openapi_docs(self):
)
if "application/json" in content["content"]:
json_content = content["content"]["application/json"]
- self.assertTrue("schema" in json_content)
try:
+ self.assertTrue("schema" in json_content)
self.verify_schema(json_content["schema"])
except AssertionError as e:
raise AssertionError(
From 03214afe42d3574029b38b47947611a622ce4fe6 Mon Sep 17 00:00:00 2001
From: Avi Upadhyayula <69180850+aviupadhyayula@users.noreply.github.com>
Date: Tue, 13 Feb 2024 00:39:24 -0500
Subject: [PATCH 22/64] Update ticketing (backend) branch (#612)
* Merge master into ticketing
* Move ticketing migration to end
---
.github/cdk/.gitignore | 3 -
.github/cdk/cdkactions.yaml | 2 -
.github/cdk/main.ts | 14 -
.github/cdk/package.json | 25 -
.github/cdk/tsconfig.json | 33 -
.github/cdk/yarn.lock | 206 --
.github/dependabot.yml | 12 -
.github/workflows/build-and-deploy.yaml | 122 +
.../cdkactions_build-and-deploy.yaml | 241 --
.github/workflows/cdkactions_validate.yaml | 28 -
.gitignore | 3 +
.pre-commit-config.yaml | 5 +-
README.md | 14 +-
backend/Dockerfile | 2 +-
backend/Pipfile | 9 +-
backend/Pipfile.lock | 2087 ++++++-------
backend/clubs/admin.py | 11 +-
backend/clubs/management/commands/populate.py | 12 +-
.../management/commands/update_club_counts.py | 36 +
.../commands/wharton_council_application.py | 135 -
.../{0090_adminnote.py => 0088_adminnote.py} | 2 +-
...0088_alter_applicationsubmission_status.py | 45 -
...0089_alter_applicationsubmission_status.py | 26 -
.../migrations/0089_auto_20230103_1239.py | 60 +
.../migrations/0090_auto_20230106_1443.py | 40 +
.../migrations/0091_applicationextension.py | 47 +
...lication_application_end_time_exception.py | 18 +
.../migrations/0092_merge_20240106_1117.py | 13 +
.../migrations/0093_auto_20240106_1153.py | 23 +
.../0094_applicationcycle_release_date.py | 18 +
.../migrations/0095_rm_field_add_count.py | 34 +
...091_cart_ticket.py => 0096_cart_ticket.py} | 2 +-
backend/clubs/mixins.py | 2 +-
backend/clubs/models.py | 92 +-
backend/clubs/serializers.py | 207 +-
backend/clubs/urls.py | 17 +-
backend/clubs/views.py | 947 +++++-
backend/pennclubs/settings/base.py | 2 +-
backend/pennclubs/settings/production.py | 7 +-
.../emails/application_extension.html | 22 +
backend/templates/emails/renew.html | 2 +-
.../templates/emails/renewal_reminder.html | 6 +-
backend/tests/clubs/test_views.py | 70 +-
frontend/components/Applications.tsx | 150 +-
.../ClubEditPage/ApplicationsCard.tsx | 66 +-
.../ClubEditPage/ApplicationsPage.tsx | 547 +++-
frontend/components/ClubPage/Actions.tsx | 37 +-
frontend/components/ClubPage/InfoBox.tsx | 6 +-
.../ClubPage/RenewalRequestDialog.tsx | 4 +-
frontend/components/CustomOption.tsx | 69 +
frontend/components/DisplayButtons.tsx | 23 +-
frontend/components/FormComponents.tsx | 90 +-
frontend/components/Header/Links.tsx | 8 +
frontend/components/ModelForm.tsx | 32 +-
frontend/components/SearchBar.tsx | 2 +-
.../Settings/WhartonApplicationCycles.tsx | 327 +++
.../Settings/WhartonApplicationStatus.tsx | 15 +
frontend/components/Submissions.tsx | 18 +-
frontend/components/common/Table.tsx | 1 +
.../application/[application]/index.tsx | 45 +-
frontend/pages/club/[club]/apply.tsx | 8 +-
frontend/pages/club/[club]/renew.tsx | 50 +-
frontend/pages/fair.tsx | 106 +-
frontend/pages/wharton/[[...slug]].tsx | 6 +
frontend/types.ts | 13 +
frontend/utils.tsx | 4 +
frontend/utils/branding.tsx | 508 ++--
k8s/main.ts | 60 +-
k8s/package.json | 57 +-
k8s/yarn.lock | 2610 ++++++++++-------
70 files changed, 5855 insertions(+), 3709 deletions(-)
delete mode 100644 .github/cdk/.gitignore
delete mode 100644 .github/cdk/cdkactions.yaml
delete mode 100644 .github/cdk/main.ts
delete mode 100644 .github/cdk/package.json
delete mode 100644 .github/cdk/tsconfig.json
delete mode 100644 .github/cdk/yarn.lock
delete mode 100644 .github/dependabot.yml
create mode 100644 .github/workflows/build-and-deploy.yaml
delete mode 100644 .github/workflows/cdkactions_build-and-deploy.yaml
delete mode 100644 .github/workflows/cdkactions_validate.yaml
create mode 100644 backend/clubs/management/commands/update_club_counts.py
delete mode 100644 backend/clubs/management/commands/wharton_council_application.py
rename backend/clubs/migrations/{0090_adminnote.py => 0088_adminnote.py} (95%)
delete mode 100644 backend/clubs/migrations/0088_alter_applicationsubmission_status.py
delete mode 100644 backend/clubs/migrations/0089_alter_applicationsubmission_status.py
create mode 100644 backend/clubs/migrations/0089_auto_20230103_1239.py
create mode 100644 backend/clubs/migrations/0090_auto_20230106_1443.py
create mode 100644 backend/clubs/migrations/0091_applicationextension.py
create mode 100644 backend/clubs/migrations/0091_clubapplication_application_end_time_exception.py
create mode 100644 backend/clubs/migrations/0092_merge_20240106_1117.py
create mode 100644 backend/clubs/migrations/0093_auto_20240106_1153.py
create mode 100644 backend/clubs/migrations/0094_applicationcycle_release_date.py
create mode 100644 backend/clubs/migrations/0095_rm_field_add_count.py
rename backend/clubs/migrations/{0091_cart_ticket.py => 0096_cart_ticket.py} (98%)
create mode 100644 backend/templates/emails/application_extension.html
create mode 100644 frontend/components/CustomOption.tsx
create mode 100644 frontend/components/Settings/WhartonApplicationCycles.tsx
diff --git a/.github/cdk/.gitignore b/.github/cdk/.gitignore
deleted file mode 100644
index 794fce505..000000000
--- a/.github/cdk/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-node_modules/
-main.js
-main.d.ts
diff --git a/.github/cdk/cdkactions.yaml b/.github/cdk/cdkactions.yaml
deleted file mode 100644
index 626767947..000000000
--- a/.github/cdk/cdkactions.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-language: typescript
-app: node main.js
diff --git a/.github/cdk/main.ts b/.github/cdk/main.ts
deleted file mode 100644
index 8a675c301..000000000
--- a/.github/cdk/main.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { App } from "cdkactions";
-import { LabsApplicationStack } from '@pennlabs/kraken';
-
-
-const app = new App();
-new LabsApplicationStack(app, {
- djangoProjectName: 'pennclubs',
- dockerImageBaseName: 'penn-clubs',
- integrationTests: true,
- integrationProps: {
- testCommand: 'docker-compose -f docker-compose.test.yaml exec -T frontend yarn integration',
- },
-});
-app.synth();
diff --git a/.github/cdk/package.json b/.github/cdk/package.json
deleted file mode 100644
index 7606d7034..000000000
--- a/.github/cdk/package.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "name": "cdk",
- "version": "0.1.0",
- "main": "main.js",
- "types": "main.ts",
- "license": "Apache-2.0",
- "private": true,
- "scripts": {
- "synth": "cdkactions synth",
- "compile": "tsc",
- "watch": "tsc -w",
- "build": "yarn compile && yarn synth",
- "upgrade-cdk": "yarn upgrade cdkactions@latest cdkactions-cli@latest"
- },
- "dependencies": {
- "@pennlabs/kraken": "^0.8.6",
- "cdkactions": "^0.2.1",
- "constructs": "^3.3.136"
- },
- "devDependencies": {
- "@types/node": "^16.7.5",
- "cdkactions-cli": "^0.2.3",
- "typescript": "^4.4.2"
- }
-}
diff --git a/.github/cdk/tsconfig.json b/.github/cdk/tsconfig.json
deleted file mode 100644
index 936e05cef..000000000
--- a/.github/cdk/tsconfig.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "compilerOptions": {
- "alwaysStrict": true,
- "charset": "utf8",
- "declaration": true,
- "experimentalDecorators": true,
- "inlineSourceMap": true,
- "inlineSources": true,
- "lib": [
- "es2018"
- ],
- "module": "CommonJS",
- "noEmitOnError": true,
- "noFallthroughCasesInSwitch": true,
- "noImplicitAny": true,
- "noImplicitReturns": true,
- "noImplicitThis": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "resolveJsonModule": true,
- "strict": true,
- "strictNullChecks": true,
- "strictPropertyInitialization": true,
- "stripInternal": true,
- "target": "ES2018"
- },
- "include": [
- "**/*.ts"
- ],
- "exclude": [
- "node_modules"
- ]
-}
diff --git a/.github/cdk/yarn.lock b/.github/cdk/yarn.lock
deleted file mode 100644
index 04c1005e0..000000000
--- a/.github/cdk/yarn.lock
+++ /dev/null
@@ -1,206 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@pennlabs/kraken@^0.8.6":
- version "0.8.6"
- resolved "https://registry.yarnpkg.com/@pennlabs/kraken/-/kraken-0.8.6.tgz#79a9d10bed36b699c526556cd69b6d81341847d1"
- integrity sha512-aBblQa/661DJ2GP3Dq1KEzCZ72ZV/Jw7z4HNZoWPxGWn+tSPwvaPkSNDpK7tT+nJmu427giGU8DLyciU79hKbA==
- dependencies:
- cdkactions "^0.2.3"
- constructs "^3.2.80"
- ts-dedent "^2.2.0"
-
-"@types/node@^16.7.5":
- version "16.7.5"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.5.tgz#96142b243977b03d99c338fdb09241d286102711"
- integrity sha512-E7SpxDXoHEpmZ9C1gSqwadhE6zPRtf3g0gJy9Y51DsImnR5TcDs3QEiV/3Q7zOM8LWaZp5Gph71NK6ElVMG1IQ==
-
-ansi-regex@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
- integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
-
-ansi-styles@^4.0.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
- integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
- dependencies:
- color-convert "^2.0.1"
-
-argparse@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
- integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-
-cdkactions-cli@^0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/cdkactions-cli/-/cdkactions-cli-0.2.3.tgz#2393682b37ab0b04c6964160b393e8d71b08118f"
- integrity sha512-qYPbzuQ1M5gQGa8NRnaWwm3iXmdqMoiHR7YTh6oYROpfBGER7kwBBb6ydFlSwKK62hE0B++by43hbEBXlHvr8A==
- dependencies:
- cdkactions "^0.2.3"
- constructs "^3.2.109"
- fs-extra "^8.1.0"
- sscaff "^1.2.0"
- yaml "^1.10.0"
- yargs "^16.2.0"
-
-cdkactions@^0.2.1, cdkactions@^0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/cdkactions/-/cdkactions-0.2.3.tgz#aa27bf720962376d54f8ef95cdfb0ab46458b966"
- integrity sha512-/DYQ2qsT6fzgZB+cmQjtPqR4aAWCqAytWbFpJK+iJLQ4jQrl6l4uMf01TLiWY3mAILS0YGlwPcoBbGvq9Jnz5g==
- dependencies:
- js-yaml "^4.0.0"
- ts-dedent "^2.0.0"
-
-cliui@^7.0.2:
- version "7.0.4"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
- integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
- dependencies:
- string-width "^4.2.0"
- strip-ansi "^6.0.0"
- wrap-ansi "^7.0.0"
-
-color-convert@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
- integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
- dependencies:
- color-name "~1.1.4"
-
-color-name@~1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
- integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
-constructs@^3.2.109, constructs@^3.2.80, constructs@^3.3.136:
- version "3.3.136"
- resolved "https://registry.yarnpkg.com/constructs/-/constructs-3.3.136.tgz#9a311737bb802f7931a1f38c5223d82fa3efd08d"
- integrity sha512-8qGuZTTXxsV3uUtqbajcQhcuu28bcuYG5jODXlXWLpd4bSo+2dMg5vKLr0dsRm/Q3B7op7io1P0opMzaGdCO5A==
-
-emoji-regex@^8.0.0:
- version "8.0.0"
- resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
- integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
-
-escalade@^3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
- integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
-
-fs-extra@^8.1.0:
- version "8.1.0"
- resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
- integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
- dependencies:
- graceful-fs "^4.2.0"
- jsonfile "^4.0.0"
- universalify "^0.1.0"
-
-get-caller-file@^2.0.5:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
- integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
-
-graceful-fs@^4.1.6, graceful-fs@^4.2.0:
- version "4.2.8"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
- integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
-
-is-fullwidth-code-point@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
- integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
-
-js-yaml@^4.0.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
- integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
- dependencies:
- argparse "^2.0.1"
-
-jsonfile@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
- integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
- optionalDependencies:
- graceful-fs "^4.1.6"
-
-require-directory@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
- integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
-
-sscaff@^1.2.0:
- version "1.2.57"
- resolved "https://registry.yarnpkg.com/sscaff/-/sscaff-1.2.57.tgz#ebd5b58ec6567f8a6684e5e2245fe2ece24b4c53"
- integrity sha512-nQwKlWrf7fls8TJibFM8rMXVZYvcfHc2pSMKNO641p83U5/Aof1KyrnF39X5m2baX9/uZfPxe6SRHBWSpLMfyA==
-
-string-width@^4.1.0, string-width@^4.2.0:
- version "4.2.2"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
- integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.0"
-
-strip-ansi@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
- integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
- dependencies:
- ansi-regex "^5.0.0"
-
-ts-dedent@^2.0.0, ts-dedent@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
- integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
-
-typescript@^4.4.2:
- version "4.4.2"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86"
- integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==
-
-universalify@^0.1.0:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
- integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
-
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
-y18n@^5.0.5:
- version "5.0.8"
- resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
- integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-
-yaml@^1.10.0:
- version "1.10.2"
- resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
- integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
-
-yargs-parser@^20.2.2:
- version "20.2.9"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
- integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
-
-yargs@^16.2.0:
- version "16.2.0"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
- integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
- dependencies:
- cliui "^7.0.2"
- escalade "^3.1.1"
- get-caller-file "^2.0.5"
- require-directory "^2.1.1"
- string-width "^4.2.0"
- y18n "^5.0.5"
- yargs-parser "^20.2.2"
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index f81e0b01f..000000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-version: 2
-updates:
- - package-ecosystem: "npm"
- directory: "/frontend"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
- - package-ecosystem: "pip"
- directory: "/backend"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
diff --git a/.github/workflows/build-and-deploy.yaml b/.github/workflows/build-and-deploy.yaml
new file mode 100644
index 000000000..358e3e6af
--- /dev/null
+++ b/.github/workflows/build-and-deploy.yaml
@@ -0,0 +1,122 @@
+# ========================================
+# Note: If you make changes to this CI/CD, please include someone from DevOps in the list of reviewers for the PR.
+# ========================================
+name: Build and Deploy Clubs
+
+on: push
+
+jobs:
+ backend-check:
+ name: "Backend Check"
+ uses: pennlabs/shared-actions/.github/workflows/django.yaml@8785a7d7b9158d8d5705a0202f5695db2c0beb97
+ with:
+ projectName: pennclubs
+ path: backend
+ flake: true
+ black: true
+
+ frontend-check:
+ name: "Frontend Check"
+ uses: pennlabs/shared-actions/.github/workflows/react-check.yaml@v0.1
+ with:
+ path: frontend
+
+ build-backend:
+ name: Build backend
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: docker/setup-qemu-action@v1
+ - uses: docker/setup-buildx-action@v1
+ - name: Cache Docker layers
+ uses: actions/cache@v2
+ with:
+ path: /tmp/.buildx-cache
+ key: buildx-build-backend
+ - name: Build/Publish
+ uses: docker/build-push-action@v2
+ with:
+ context: backend
+ file: backend/Dockerfile
+ push: false
+ cache-from: type=local,src=/tmp/.buildx-cache,type=registry,ref=pennlabs/penn-clubs-backend:latest
+ cache-to: type=local,dest=/tmp/.buildx-cache
+ tags: pennlabs/penn-clubs-backend:latest,pennlabs/penn-clubs-backend:${{ github.sha }}
+ outputs: type=docker,dest=/tmp/image.tar
+ - uses: actions/upload-artifact@v2
+ with:
+ name: build-backend
+ path: /tmp/image.tar
+ needs: backend-check
+
+ build-frontend:
+ name: Build frontend
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: docker/setup-qemu-action@v1
+ - uses: docker/setup-buildx-action@v1
+ - name: Cache Docker layers
+ uses: actions/cache@v2
+ with:
+ path: /tmp/.buildx-cache
+ key: buildx-build-frontend
+ - name: Build/Publish
+ uses: docker/build-push-action@v2
+ with:
+ context: frontend
+ file: frontend/Dockerfile
+ push: false
+ cache-from: type=local,src=/tmp/.buildx-cache,type=registry,ref=pennlabs/penn-clubs-frontend:latest
+ cache-to: type=local,dest=/tmp/.buildx-cache
+ tags: pennlabs/penn-clubs-frontend:latest,pennlabs/penn-clubs-frontend:${{ github.sha }}
+ outputs: type=docker,dest=/tmp/image.tar
+ - uses: actions/upload-artifact@v2
+ with:
+ name: build-frontend
+ path: /tmp/image.tar
+ needs: frontend-check
+
+ publish:
+ name: Publish Images
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/master'
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/download-artifact@v2
+ - uses: geekyeggo/delete-artifact@v1
+ with:
+ name: |-
+ build-backend
+ build-frontend
+ - name: Load docker images
+ run: |-
+ docker load --input build-backend/image.tar
+ docker load --input build-frontend/image.tar
+ - uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+ - name: Push docker images
+ run: |-
+ docker push -a pennlabs/penn-clubs-backend
+ docker push -a pennlabs/penn-clubs-frontend
+ needs:
+ - build-backend
+ - build-frontend
+
+ deploy:
+ name: "Deploy"
+ uses: pennlabs/shared-actions/.github/workflows/deployment.yaml@v0.1
+
+ with:
+ githubRef: ${{ github.ref }}
+ gitSha: ${{ github.sha }}
+
+ secrets:
+ AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
+ GH_AWS_ACCESS_KEY_ID: ${{ secrets.GH_AWS_ACCESS_KEY_ID }}
+ GH_AWS_SECRET_ACCESS_KEY: ${{ secrets.GH_AWS_SECRET_ACCESS_KEY }}
+
+ needs:
+ - publish
diff --git a/.github/workflows/cdkactions_build-and-deploy.yaml b/.github/workflows/cdkactions_build-and-deploy.yaml
deleted file mode 100644
index 46ae7ee89..000000000
--- a/.github/workflows/cdkactions_build-and-deploy.yaml
+++ /dev/null
@@ -1,241 +0,0 @@
-# Generated by cdkactions. Do not modify
-# Generated as part of the 'application' stack.
-name: Build and Deploy
-on: push
-jobs:
- django-check:
- name: Django Check
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - name: Cache
- uses: actions/cache@v2
- with:
- path: ~/.local/share/virtualenvs
- key: v0-${{ hashFiles('backend/Pipfile.lock') }}
- - name: Install Dependencies
- run: |-
- cd backend
- pip install pipenv
- pipenv install --deploy --dev
- - name: Lint (flake8)
- run: |-
- cd backend
- pipenv run flake8 .
- - name: Lint (black)
- run: |-
- cd backend
- pipenv run black --check .
- - name: Test (run in parallel)
- run: |-
- cd backend
- pipenv run coverage run --concurrency=multiprocessing manage.py test --settings=pennclubs.settings.ci --parallel
- pipenv run coverage combine
- - name: Upload Code Coverage
- run: |-
- ROOT=$(pwd)
- cd backend
- pipenv run codecov --root $ROOT --flags backend
- container:
- image: python:3.8-buster
- env:
- DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres
- services:
- postgres:
- image: postgres:12
- env:
- POSTGRES_USER: postgres
- POSTGRES_DB: postgres
- POSTGRES_PASSWORD: postgres
- options: "--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5"
- build-backend:
- name: Build backend
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: docker/setup-qemu-action@v1
- - uses: docker/setup-buildx-action@v1
- - name: Cache Docker layers
- uses: actions/cache@v2
- with:
- path: /tmp/.buildx-cache
- key: buildx-build-backend
- - name: Build/Publish
- uses: docker/build-push-action@v2
- with:
- context: backend
- file: backend/Dockerfile
- push: false
- cache-from: type=local,src=/tmp/.buildx-cache,type=registry,ref=pennlabs/penn-clubs-backend:latest
- cache-to: type=local,dest=/tmp/.buildx-cache
- tags: pennlabs/penn-clubs-backend:latest,pennlabs/penn-clubs-backend:${{ github.sha }}
- outputs: type=docker,dest=/tmp/image.tar
- - uses: actions/upload-artifact@v2
- with:
- name: build-backend
- path: /tmp/image.tar
- needs: django-check
- react-check:
- name: React Check
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - name: Cache
- uses: actions/cache@v2
- with:
- path: "**/node_modules"
- key: v0-${{ hashFiles('frontend/yarn.lock') }}
- - name: Install Dependencies
- run: |-
- cd frontend
- yarn install --frozen-lockfile
- - name: Lint
- run: |-
- cd frontend
- yarn lint
- - name: Test
- run: |-
- cd frontend
- yarn test
- - name: Upload Code Coverage
- run: |-
- ROOT=$(pwd)
- cd frontend
- yarn run codecov -p $ROOT -F frontend
- container:
- image: node:14
- build-frontend:
- name: Build frontend
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: docker/setup-qemu-action@v1
- - uses: docker/setup-buildx-action@v1
- - name: Cache Docker layers
- uses: actions/cache@v2
- with:
- path: /tmp/.buildx-cache
- key: buildx-build-frontend
- - name: Build/Publish
- uses: docker/build-push-action@v2
- with:
- context: frontend
- file: frontend/Dockerfile
- push: false
- cache-from: type=local,src=/tmp/.buildx-cache,type=registry,ref=pennlabs/penn-clubs-frontend:latest
- cache-to: type=local,dest=/tmp/.buildx-cache
- tags: pennlabs/penn-clubs-frontend:latest,pennlabs/penn-clubs-frontend:${{ github.sha }}
- outputs: type=docker,dest=/tmp/image.tar
- - uses: actions/upload-artifact@v2
- with:
- name: build-frontend
- path: /tmp/image.tar
- needs: react-check
- integration-tests:
- name: Integration Tests
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: actions/download-artifact@v2
- - name: Load docker images
- run: |-
- docker load --input build-backend/image.tar
- docker load --input build-frontend/image.tar
- - name: Run docker compose
- run: |-
- mkdir -p /tmp/test-results
- docker-compose -f docker-compose.test.yaml up -d
- - name: Wait for backend
- run: |-
- for try in {1..20}; do
- docker-compose -f docker-compose.test.yaml exec -T backend python manage.py migrate --check && break
- sleep 5
- done
- - name: Populate backend
- run: docker-compose -f docker-compose.test.yaml exec -T backend python manage.py populate
- - name: Run integration tests
- run: docker-compose -f docker-compose.test.yaml exec -T frontend yarn integration
- - name: Delete artifacts when no longer needed
- if: failure() || github.ref != 'refs/heads/master'
- uses: geekyeggo/delete-artifact@v1
- with:
- name: |-
- build-backend
- build-frontend
- - name: Print logs on failure
- if: failure()
- run: docker-compose -f docker-compose.test.yaml logs
- - name: Upload artifacts on failure
- if: failure()
- uses: actions/upload-artifact@v2
- with:
- name: cypress-output
- path: /tmp/test-results
- env:
- GIT_SHA: ${{ github.sha }}
- needs:
- - build-backend
- - build-frontend
- post-integration-publish:
- name: Publish Images
- runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/master'
- steps:
- - uses: actions/checkout@v2
- - uses: actions/download-artifact@v2
- - uses: geekyeggo/delete-artifact@v1
- with:
- name: |-
- build-backend
- build-frontend
- - name: Load docker images
- run: |-
- docker load --input build-backend/image.tar
- docker load --input build-frontend/image.tar
- - uses: docker/login-action@v1
- with:
- username: ${{ secrets.DOCKER_USERNAME }}
- password: ${{ secrets.DOCKER_PASSWORD }}
- - name: Push docker images
- run: |-
- docker push -a pennlabs/penn-clubs-backend
- docker push -a pennlabs/penn-clubs-frontend
- needs: integration-tests
- deploy:
- runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/master'
- steps:
- - uses: actions/checkout@v2
- - id: synth
- name: Synth cdk8s manifests
- run: |-
- cd k8s
- yarn install --frozen-lockfile
-
- # get repo name (by removing owner/organization)
- export RELEASE_NAME=${REPOSITORY#*/}
-
- # Export RELEASE_NAME as an output
- echo "::set-output name=RELEASE_NAME::$RELEASE_NAME"
-
- yarn build
- env:
- GIT_SHA: ${{ github.sha }}
- REPOSITORY: ${{ github.repository }}
- AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
- - name: Deploy
- run: |-
- aws eks --region us-east-1 update-kubeconfig --name production --role-arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/kubectl
-
- # get repo name from synth step
- RELEASE_NAME=${{ steps.synth.outputs.RELEASE_NAME }}
-
- # Deploy
- kubectl apply -f k8s/dist/ -l app.kubernetes.io/component=certificate
- kubectl apply -f k8s/dist/ --prune -l app.kubernetes.io/part-of=$RELEASE_NAME
- env:
- AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
- AWS_ACCESS_KEY_ID: ${{ secrets.GH_AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.GH_AWS_SECRET_ACCESS_KEY }}
- needs:
- - post-integration-publish
diff --git a/.github/workflows/cdkactions_validate.yaml b/.github/workflows/cdkactions_validate.yaml
deleted file mode 100644
index 73834d779..000000000
--- a/.github/workflows/cdkactions_validate.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-# Generated by cdkactions. Do not modify
-# Generated as part of the 'validate' stack.
-name: Validate cdkactions manifests
-on: push
-jobs:
- validate:
- name: Validate cdkactions manifests
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- with:
- token: ${{ github.token }}
- - name: Validate manifests
- run: |-
- cd .github/cdk
- yarn install
- yarn build
- git --no-pager diff ../workflows
- git diff-index --quiet HEAD -- ../workflows
- - name: Push updated manifests
- if: "false"
- run: |-
- cd .github/workflows
- git config user.name github-actions
- git config user.email github-actions[bot]@users.noreply.github.com
- git add .
- git commit -m "Update cdkactions manifests" || exit 0
- git push
diff --git a/.gitignore b/.gitignore
index 3cd5296e7..35a1877f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
# Python files
__pycache__/
*.pyc
+.python-version
# Distribution
/frontend/public/storybook/
@@ -27,6 +28,8 @@ db.sqlite3
# React
node_modules/
+.yarn
+.yarnrc.yml
.next/
# Development Enviroment
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9f5abe424..64a465e47 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,11 +4,12 @@ repos:
hooks:
- id: black
name: black
- entry: cd backend && pipenv run black
+ entry: bash -c "export PIPENV_IGNORE_VIRTUALENVS=1 && cd backend && pipenv run black ."
language: python
types: [python]
require_serial: true
files: ^backend/
+ pass_filenames: false
- id: isort
name: isort
entry: isort
@@ -26,7 +27,7 @@ repos:
args: [--config, backend/setup.cfg]
- id: frontend
name: Yarn Linter
- entry: yarn --cwd frontend lint
+ entry: bash -c "cd frontend && yarn lint"
language: system
files: ^frontend/
require_serial: false
diff --git a/README.md b/README.md
index e9552e195..ff2f12a9b 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ Official React-based website for Penn Labs' club directory and events listings.
The REST API written in Django for Penn Clubs infrastructure.
## Installation
+
You will need to start both the backend and the frontend to do Penn Clubs development.
Questions? Check out our [extended guide](https://github.com/pennlabs/penn-clubs/wiki/Development-Guide#windows-development) for FAQs for both Mac and Windows.
@@ -16,6 +17,7 @@ Questions? Check out our [extended guide](https://github.com/pennlabs/penn-clubs
Running the backend requires [Python 3](https://www.python.org/downloads/).
In production, you will need to set the following environment variables:
+
- `NEXT_PUBLIC_SITE_NAME` (optional, defaults to `clubs`)
- `SECRET_KEY`
- `SENTRY_URL`
@@ -27,6 +29,7 @@ In production, you will need to set the following environment variables:
- `LABS_CLIENT_SECRET` (from Platform)
To run the server, `cd` to the folder where you cloned `penn-clubs`. Then run:
+
- `cd backend`
Setting up `psycopg2` (this is necessary if you want to be able to modify
@@ -42,14 +45,14 @@ dependencies, you can revisit later if not)
- Windows
- `$ apt-get install gcc python3-dev libpq-dev`
-Now, you can run
+Now, you can run
- `$ pipenv install` to install Python dependencies. This may take a few
minutes. Optionally include the `--dev` argument if you are installing locally
for development. If you skipped installing `psycopg2` earlier, you might see
an error with locking -- this is expected!
- `$ pipenv shell`
-- `$ pre-commit install`
+- `$ pre-commit install`
- `$ ./manage.py migrate` OR `$ python3 manage.py migrate`
- `$ ./manage.py populate` OR `$ python3 manage.py populate` (in development,
to populate the database with dummy data)
@@ -57,9 +60,14 @@ Now, you can run
### Frontend
-Running the frontend requires [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/getting-started/install).
+Running the frontend requires [Node.js](https://nodejs.org/en/) and
+[Yarn](https://yarnpkg.com/getting-started/install).
+
+**Please ensure you are using Node 14**. Our codebase does not support other
+versions of Node (v14.21.3 is stable).
You will need to set the following environment variables on the frontend:
+
- `NEXT_PUBLIC_GOOGLE_API_KEY`
- `NEXT_PUBLIC_SITE_NAME` (Optional)
- Specify `clubs` to show Penn Clubs and `fyh` to show Hub@Penn.
diff --git a/backend/Dockerfile b/backend/Dockerfile
index 9c9f86b52..b58dc4d80 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -1,4 +1,4 @@
-FROM pennlabs/django-base:a142aa6975ee293bbc8a09ef0b81998ce7063dd3
+FROM pennlabs/django-base:9c4f31bf1af44219d0f9019271a0033a222291c2-3.8.5
LABEL maintainer="Penn Labs"
diff --git a/backend/Pipfile b/backend/Pipfile
index 01c5b61a8..15e55c7a9 100644
--- a/backend/Pipfile
+++ b/backend/Pipfile
@@ -4,10 +4,9 @@ url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
-codecov = "*"
black = "==19.10b0"
unittest-xml-reporting = ">=3.0.2"
-flake8 = "*"
+flake8 = "==5.0.3"
flake8-isort = "*"
isort = "*"
flake8-quotes = "*"
@@ -32,7 +31,6 @@ pillow = "*"
django-phonenumber-field = "*"
phonenumbers = "*"
qrcode = "*"
-drf-renderer-xlsx = "==0.3.9"
python-dateutil = "*"
psycopg2 = "*"
django-simple-history = "*"
@@ -54,6 +52,11 @@ tblib = "*"
pre-commit = "*"
django-clone = "*"
click = "==8.0.4"
+jinja2 = "*"
+pandas = "*"
+drf-excel = "*"
+numpy = "*"
+coverage = "*"
[requires]
python_version = "3"
diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock
index 6c195adb4..3831da635 100644
--- a/backend/Pipfile.lock
+++ b/backend/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "1e5ed3497a97d54da1163f9def2f5aee2feaf2aa537a361d42376f30cb8cac4f"
+ "sha256": "d2ae42ea3dc4d5de0bc9f2be6e0603264bfe0f248a0209f224b40ef941770a35"
},
"pipfile-spec": 6,
"requires": {
@@ -16,20 +16,13 @@
]
},
"default": {
- "aioredis": {
- "hashes": [
- "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a",
- "sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3"
- ],
- "version": "==1.3.1"
- },
"anyio": {
"hashes": [
- "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b",
- "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"
+ "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421",
+ "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"
],
"markers": "python_full_version >= '3.6.2'",
- "version": "==3.6.1"
+ "version": "==3.6.2"
},
"arrow": {
"hashes": [
@@ -41,49 +34,49 @@
},
"asgiref": {
"hashes": [
- "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4",
- "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"
+ "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac",
+ "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"
],
"markers": "python_version >= '3.7'",
- "version": "==3.5.2"
+ "version": "==3.6.0"
},
"async-timeout": {
"hashes": [
"sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
"sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
],
- "markers": "python_version >= '3.6'",
+ "markers": "python_full_version <= '3.11.2'",
"version": "==4.0.2"
},
"attrs": {
"hashes": [
- "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6",
- "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"
+ "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04",
+ "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"
],
- "markers": "python_version >= '3.5'",
- "version": "==22.1.0"
+ "markers": "python_version >= '3.7'",
+ "version": "==23.1.0"
},
"autobahn": {
"hashes": [
- "sha256:8b462ea2e6aad6b4dc0ed45fb800b6cbfeb0325e7fe6983907f122f2be4a1fe9"
+ "sha256:c5ef8ca7422015a1af774a883b8aef73d4954c9fcd182c9b5244e08e973f7c3a"
],
"markers": "python_version >= '3.7'",
- "version": "==22.7.1"
+ "version": "==23.1.2"
},
"automat": {
"hashes": [
- "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33",
- "sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111"
+ "sha256:c3164f8742b9dc440f3682482d32aaff7bb53f71740dd018533f9de286b64180",
+ "sha256:e56beb84edad19dcc11d30e8d9b895f75deeb5ef5e96b84a467066b3b84bb04e"
],
- "version": "==20.2.0"
+ "version": "==22.10.0"
},
"beautifulsoup4": {
"hashes": [
- "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
- "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
+ "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da",
+ "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"
],
- "markers": "python_full_version >= '3.6.0'",
- "version": "==4.11.1"
+ "markers": "python_version >= '3.6'",
+ "version": "==4.12.2"
},
"bleach": {
"hashes": [
@@ -95,19 +88,19 @@
},
"boto3": {
"hashes": [
- "sha256:6194763348545bb1669ce8d03ba104be1ba822daa184613aa10b9303a6a79017",
- "sha256:be151711bbb4db53e85dd5bbe506002ce6f2f21fc4e45fcf6d2cf356d32cc4c6"
+ "sha256:1ff703152553f4d5fc9774071d114dbf06ec661eb1b29b6051f6b1f9d0c24873",
+ "sha256:d0ed43228952b55c9f44d1c733f74656418c39c55dbe36bc37feeef6aa583ded"
],
"index": "pypi",
- "version": "==1.24.84"
+ "version": "==1.26.118"
},
"botocore": {
"hashes": [
- "sha256:11f05d2acdf9a5f722856704b7b951b180647fb4340e1b5048b27273dc323909",
- "sha256:da15026329706caf83323d84996f5ff5c527837347633fca9b3b1be0efa60841"
+ "sha256:44cb088a73b02dd716c5c5715143a64d5f10388957285246e11f3cc893eebf9d",
+ "sha256:b51fc5d50cbc43edaf58b3ec4fa933b82755801c453bf8908c8d3e70ae1142c1"
],
"markers": "python_version >= '3.7'",
- "version": "==1.27.84"
+ "version": "==1.29.118"
},
"bs4": {
"hashes": [
@@ -118,11 +111,11 @@
},
"certifi": {
"hashes": [
- "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14",
- "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"
+ "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
+ "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
],
"markers": "python_version >= '3.6'",
- "version": "==2022.9.24"
+ "version": "==2022.12.7"
},
"cffi": {
"hashes": [
@@ -211,19 +204,92 @@
},
"channels-redis": {
"hashes": [
- "sha256:78e4a2f2b2a744fe5a87848ec36b5ee49f522c6808cefe6c583663d0d531faa8",
- "sha256:ba7e2ad170f273c372812dd32aaac102d68d4e508172abb1cfda3160b7333890"
+ "sha256:3696f5b9fe367ea495d402ba83d7c3c99e8ca0e1354ff8d913535976ed0abf73",
+ "sha256:6bd4f75f4ab4a7db17cee495593ace886d7e914c66f8214a1f247ff6659c073a"
],
"index": "pypi",
- "version": "==3.4.1"
+ "version": "==4.1.0"
},
"charset-normalizer": {
"hashes": [
- "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
- "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
+ "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6",
+ "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1",
+ "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e",
+ "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373",
+ "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62",
+ "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230",
+ "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be",
+ "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c",
+ "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0",
+ "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448",
+ "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f",
+ "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649",
+ "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d",
+ "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0",
+ "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706",
+ "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a",
+ "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59",
+ "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23",
+ "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5",
+ "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb",
+ "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e",
+ "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e",
+ "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c",
+ "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28",
+ "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d",
+ "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41",
+ "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974",
+ "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce",
+ "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f",
+ "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1",
+ "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d",
+ "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8",
+ "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017",
+ "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31",
+ "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7",
+ "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8",
+ "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e",
+ "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14",
+ "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd",
+ "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d",
+ "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795",
+ "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b",
+ "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b",
+ "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b",
+ "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203",
+ "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f",
+ "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19",
+ "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1",
+ "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a",
+ "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac",
+ "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9",
+ "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0",
+ "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137",
+ "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f",
+ "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6",
+ "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5",
+ "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909",
+ "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f",
+ "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0",
+ "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324",
+ "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755",
+ "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb",
+ "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854",
+ "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c",
+ "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60",
+ "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84",
+ "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0",
+ "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b",
+ "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1",
+ "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531",
+ "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1",
+ "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11",
+ "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326",
+ "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df",
+ "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"
],
- "markers": "python_full_version >= '3.6.0'",
- "version": "==2.1.1"
+ "markers": "python_version >= '3.7'",
+ "version": "==3.1.0"
},
"click": {
"hashes": [
@@ -248,37 +314,88 @@
],
"version": "==15.1.0"
},
+ "coverage": {
+ "hashes": [
+ "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34",
+ "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e",
+ "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7",
+ "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b",
+ "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3",
+ "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985",
+ "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95",
+ "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2",
+ "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a",
+ "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74",
+ "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd",
+ "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af",
+ "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54",
+ "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865",
+ "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214",
+ "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54",
+ "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe",
+ "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0",
+ "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321",
+ "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446",
+ "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e",
+ "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527",
+ "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12",
+ "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f",
+ "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f",
+ "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84",
+ "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479",
+ "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e",
+ "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873",
+ "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70",
+ "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0",
+ "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977",
+ "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51",
+ "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28",
+ "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1",
+ "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254",
+ "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1",
+ "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd",
+ "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689",
+ "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d",
+ "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543",
+ "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9",
+ "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637",
+ "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071",
+ "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482",
+ "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1",
+ "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b",
+ "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5",
+ "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a",
+ "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393",
+ "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a",
+ "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"
+ ],
+ "index": "pypi",
+ "version": "==7.3.0"
+ },
"cryptography": {
"hashes": [
- "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a",
- "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f",
- "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0",
- "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407",
- "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7",
- "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6",
- "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153",
- "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750",
- "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad",
- "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6",
- "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b",
- "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5",
- "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a",
- "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d",
- "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d",
- "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294",
- "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0",
- "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a",
- "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac",
- "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61",
- "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013",
- "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e",
- "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb",
- "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9",
- "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd",
- "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"
+ "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440",
+ "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288",
+ "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b",
+ "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958",
+ "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b",
+ "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d",
+ "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a",
+ "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404",
+ "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b",
+ "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e",
+ "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2",
+ "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c",
+ "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b",
+ "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9",
+ "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b",
+ "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636",
+ "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99",
+ "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e",
+ "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9"
],
"markers": "python_version >= '3.6'",
- "version": "==38.0.1"
+ "version": "==40.0.2"
},
"daphne": {
"hashes": [
@@ -312,51 +429,51 @@
},
"dj-database-url": {
"hashes": [
- "sha256:ccf3e8718f75ddd147a1e212fca88eecdaa721759ee48e38b485481c77bca3dc",
- "sha256:cd354a3b7a9136d78d64c17b2aec369e2ae5616fbca6bfbe435ef15bb372ce39"
+ "sha256:80a115bd7675c9fe14a900b2f8b5c8b1822b5a279b333bf9b2804de681656c7c",
+ "sha256:87be5f7c4c83d9b3d8ce94b834f96cea14b3986f3629aac097afdd9318d7b098"
],
"index": "pypi",
- "version": "==1.0.0"
+ "version": "==1.3.0"
},
"django": {
"hashes": [
- "sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713",
- "sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b"
+ "sha256:08208dfe892eb64fff073ca743b3b952311104f939e7f6dae954fe72dcc533ba",
+ "sha256:4d492d9024c7b3dfababf49f94511ab6a58e2c9c3c7207786f1ba4eb77750706"
],
"index": "pypi",
- "version": "==3.2.15"
+ "version": "==3.2.18"
},
"django-clone": {
"hashes": [
- "sha256:0d12b4d976ed9be5ac19108f8551f017f79042b6676307c92fb8cfc530df861a",
- "sha256:f3bc1e06799a82770da6c8fd6abcdbf1fd41390e7ea14a0cc5a8f12c0765d36e"
+ "sha256:4be26e42bccd14e0ce24e3fa8d06744f904194e6ed7669d01034f7e81865e2e4",
+ "sha256:d44521a1680a0adbfff77429d1a4f9906ead3b31e5ee206d849883e32a6fd61f"
],
"index": "pypi",
- "version": "==3.0.6"
+ "version": "==5.3.1"
},
"django-cors-headers": {
"hashes": [
- "sha256:37e42883b5f1f2295df6b4bba96eb2417a14a03270cb24b2a07f021cd4487cf4",
- "sha256:f9dc6b4e3f611c3199700b3e5f3398c28757dcd559c2f82932687f3d0443cfdf"
+ "sha256:5fbd58a6fb4119d975754b2bc090f35ec160a8373f276612c675b00e8a138739",
+ "sha256:684180013cc7277bdd8702b80a3c5a4b3fcae4abb2bf134dceb9f5dfe300228e"
],
"index": "pypi",
- "version": "==3.13.0"
+ "version": "==3.14.0"
},
"django-labs-accounts": {
"hashes": [
- "sha256:32cf0f705c53fb4624eea7326c77e37706f496462bb4125fb488547a3af187b2",
- "sha256:fa537531f019c7668251455b1e586bcd6aa1dfb748198a6c1a5ef96972b6eae4"
+ "sha256:59e90ad8cb5201da5bbdbf68fab99eb25ca9a5d1ab577e0772ecbffcb470e15f",
+ "sha256:d360e13776a56998871289a66e855637ca740873f540da6923195e79d098491d"
],
"index": "pypi",
- "version": "==0.8.0"
+ "version": "==0.9.2"
},
"django-phonenumber-field": {
"hashes": [
- "sha256:dab78094e83f4b1276effca9903e6728e940d055b00cc8589ad5b8a22cb6a03b",
- "sha256:f1aaee276b18a8f0bf503d52eda183965ca164a6379c1e70f73718bcc8a91345"
+ "sha256:9edad2b2602af25f2aefc73c4cf53eaf7abf9e17d73c1c4372bd3052bebb26f9",
+ "sha256:de3e47b986b4959949762c16fd8fe26b3e462ef3e5531ed00950bd20c698576a"
],
"index": "pypi",
- "version": "==7.0.0"
+ "version": "==7.0.2"
},
"django-redis": {
"hashes": [
@@ -376,18 +493,19 @@
},
"django-simple-history": {
"hashes": [
- "sha256:eab6dbddb7da756cc5579f7d6f28a32b2e8bb1697a5c37794d96309793ffec38"
+ "sha256:2313d2d346f15a1e7a92adb3b6696b226f1cd0c1d920869ec40c4c4076614c41",
+ "sha256:dc1f98e558a0a1e0b6371c3b8efb85f86e02a6db56e83d0ec198343b7408d00a"
],
"index": "pypi",
- "version": "==3.1.1"
+ "version": "==3.3.0"
},
"django-storages": {
"hashes": [
- "sha256:3540b45618b04be2c867c0982e8d2bd8e34f84dae922267fcebe4691fb93daf0",
- "sha256:b3d98ecc09f1b1627c2b2cf430964322ce4e08617dbf9b4236c16a32875a1e0b"
+ "sha256:31dc5a992520be571908c4c40d55d292660ece3a55b8141462b4e719aa38eab3",
+ "sha256:cbadd15c909ceb7247d4ffc503f12a9bec36999df8d0bef7c31e57177d512688"
],
"index": "pypi",
- "version": "==1.13.1"
+ "version": "==1.13.2"
},
"djangorestframework": {
"hashes": [
@@ -397,21 +515,21 @@
"index": "pypi",
"version": "==3.14.0"
},
- "drf-nested-routers": {
+ "drf-excel": {
"hashes": [
- "sha256:01aa556b8c08608bb74fb34f6ca065a5183f2cda4dc0478192cc17a2581d71b0",
- "sha256:996b77f3f4dfaf64569e7b8f04e3919945f90f95366838ca5b8bed9dd709d6c5"
+ "sha256:01905346446f699a03ffefe97edeff7e0d83e707028df2d593bf18381a148872",
+ "sha256:3ef1ce054c52f9850c46f4d4d3344e7fc8e6a95d9d0e0fb666d0bb6c37c38666"
],
"index": "pypi",
- "version": "==0.93.4"
+ "version": "==2.3.0"
},
- "drf-renderer-xlsx": {
+ "drf-nested-routers": {
"hashes": [
- "sha256:2aca180c71b088b1f8fa9adecb675f369f88cb89d46fd36f267ae166dc58332f",
- "sha256:da19cbd973c59f8b5a0aa3d46f5888d3eddccf1c7a424a42d6748c81d4e1148f"
+ "sha256:01aa556b8c08608bb74fb34f6ca065a5183f2cda4dc0478192cc17a2581d71b0",
+ "sha256:996b77f3f4dfaf64569e7b8f04e3919945f90f95366838ca5b8bed9dd709d6c5"
],
"index": "pypi",
- "version": "==0.3.9"
+ "version": "==0.93.4"
},
"et-xmlfile": {
"hashes": [
@@ -421,20 +539,21 @@
"markers": "python_version >= '3.6'",
"version": "==1.1.0"
},
- "filelock": {
+ "exceptiongroup": {
"hashes": [
- "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc",
- "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"
+ "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9",
+ "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"
],
- "markers": "python_version >= '3.7'",
- "version": "==3.8.0"
+ "markers": "python_version < '3.11'",
+ "version": "==1.1.3"
},
- "future": {
+ "filelock": {
"hashes": [
- "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
+ "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9",
+ "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"
],
- "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==0.18.2"
+ "markers": "python_version >= '3.7'",
+ "version": "==3.12.0"
},
"gunicorn": {
"hashes": [
@@ -452,53 +571,6 @@
"markers": "python_version >= '3.7'",
"version": "==0.14.0"
},
- "hiredis": {
- "hashes": [
- "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e",
- "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27",
- "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163",
- "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc",
- "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26",
- "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e",
- "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579",
- "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a",
- "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048",
- "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87",
- "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63",
- "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54",
- "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05",
- "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb",
- "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea",
- "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5",
- "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e",
- "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc",
- "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99",
- "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a",
- "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581",
- "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426",
- "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db",
- "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a",
- "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a",
- "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d",
- "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443",
- "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79",
- "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d",
- "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9",
- "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d",
- "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485",
- "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5",
- "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048",
- "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0",
- "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6",
- "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41",
- "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298",
- "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce",
- "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
- "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==2.0.0"
- },
"httptools": {
"hashes": [
"sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9",
@@ -564,26 +636,34 @@
},
"identify": {
"hashes": [
- "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6",
- "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97"
+ "sha256:f0faad595a4687053669c112004178149f6c326db71ee999ae4636685753ad2f",
+ "sha256:f7a93d6cf98e29bd07663c60728e7a4057615068d7a639d132dc883b2d54d31e"
],
"markers": "python_version >= '3.7'",
- "version": "==2.5.5"
+ "version": "==2.5.22"
},
"idna": {
"hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
],
- "markers": "python_version >= '3.5'",
+ "markers": "python_full_version >= '3.5.0'",
"version": "==3.4"
},
"incremental": {
"hashes": [
- "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57",
- "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"
+ "sha256:912feeb5e0f7e0188e6f42241d2f450002e11bbc0937c65865045854c24c0bd0",
+ "sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51"
],
- "version": "==21.3.0"
+ "version": "==22.10.0"
+ },
+ "jinja2": {
+ "hashes": [
+ "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
+ "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
+ ],
+ "index": "pypi",
+ "version": "==3.1.2"
},
"jmespath": {
"hashes": [
@@ -595,144 +675,225 @@
},
"jsonref": {
"hashes": [
- "sha256:b1e82fa0b62e2c2796a13e5401fe51790b248f6d9bf9d7212a3e31a3501b291f",
- "sha256:f3c45b121cf6257eafabdc3a8008763aed1cd7da06dbabc59a9e4d2a5e4e6697"
+ "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552",
+ "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9"
],
"index": "pypi",
- "version": "==0.2"
+ "version": "==1.1.0"
+ },
+ "jwcrypto": {
+ "hashes": [
+ "sha256:80a35e9ed1b3b2c43ce03d92c5d48e6d0b6647e2aa2618e4963448923d78a37b"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==1.4.2"
},
"lxml": {
"hashes": [
- "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318",
- "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c",
- "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b",
- "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000",
- "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73",
- "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d",
- "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb",
- "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8",
- "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2",
- "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345",
- "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94",
- "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e",
- "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b",
- "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc",
- "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a",
- "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9",
- "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc",
- "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387",
- "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb",
- "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7",
- "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4",
- "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97",
- "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67",
- "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627",
- "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7",
- "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd",
- "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3",
- "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7",
- "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130",
- "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b",
- "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036",
- "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785",
- "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca",
- "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91",
- "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc",
- "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536",
- "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391",
- "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3",
- "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d",
- "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21",
- "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3",
- "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d",
- "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29",
- "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715",
- "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed",
- "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25",
- "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c",
- "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785",
- "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837",
- "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4",
- "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b",
- "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2",
- "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067",
- "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448",
- "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d",
- "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2",
- "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc",
- "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c",
- "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5",
- "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84",
- "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8",
- "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf",
- "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7",
- "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e",
- "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb",
- "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b",
- "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3",
- "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad",
- "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8",
- "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"
+ "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7",
+ "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726",
+ "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03",
+ "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140",
+ "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a",
+ "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05",
+ "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03",
+ "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419",
+ "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4",
+ "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e",
+ "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67",
+ "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50",
+ "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894",
+ "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf",
+ "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947",
+ "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1",
+ "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd",
+ "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3",
+ "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92",
+ "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3",
+ "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457",
+ "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74",
+ "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf",
+ "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1",
+ "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4",
+ "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975",
+ "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5",
+ "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe",
+ "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7",
+ "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1",
+ "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2",
+ "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409",
+ "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f",
+ "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f",
+ "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5",
+ "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24",
+ "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e",
+ "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4",
+ "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a",
+ "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c",
+ "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de",
+ "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f",
+ "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b",
+ "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5",
+ "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7",
+ "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a",
+ "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c",
+ "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9",
+ "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e",
+ "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab",
+ "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941",
+ "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5",
+ "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45",
+ "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7",
+ "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892",
+ "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746",
+ "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c",
+ "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53",
+ "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe",
+ "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184",
+ "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38",
+ "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df",
+ "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9",
+ "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b",
+ "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2",
+ "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0",
+ "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda",
+ "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b",
+ "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5",
+ "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380",
+ "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33",
+ "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8",
+ "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1",
+ "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889",
+ "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9",
+ "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f",
+ "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==4.9.1"
+ "version": "==4.9.2"
+ },
+ "markupsafe": {
+ "hashes": [
+ "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed",
+ "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc",
+ "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2",
+ "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460",
+ "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7",
+ "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0",
+ "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1",
+ "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa",
+ "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03",
+ "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323",
+ "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65",
+ "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013",
+ "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036",
+ "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f",
+ "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4",
+ "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419",
+ "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2",
+ "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619",
+ "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a",
+ "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a",
+ "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd",
+ "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7",
+ "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666",
+ "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65",
+ "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859",
+ "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625",
+ "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff",
+ "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156",
+ "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd",
+ "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba",
+ "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f",
+ "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1",
+ "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094",
+ "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a",
+ "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513",
+ "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed",
+ "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d",
+ "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3",
+ "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147",
+ "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c",
+ "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603",
+ "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601",
+ "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a",
+ "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1",
+ "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d",
+ "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3",
+ "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54",
+ "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2",
+ "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6",
+ "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==2.1.2"
},
"msgpack": {
"hashes": [
- "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467",
- "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae",
- "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92",
- "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef",
- "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624",
- "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227",
- "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88",
- "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9",
- "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8",
- "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd",
- "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6",
- "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55",
- "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e",
- "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2",
- "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44",
- "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6",
- "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9",
- "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab",
- "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae",
- "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa",
- "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9",
- "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e",
- "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250",
- "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce",
- "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075",
- "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236",
- "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae",
- "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e",
- "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f",
- "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08",
- "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6",
- "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d",
- "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43",
- "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1",
- "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6",
- "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0",
- "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c",
- "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff",
- "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db",
- "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243",
- "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661",
- "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba",
- "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e",
- "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb",
- "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52",
- "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6",
- "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1",
- "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f",
- "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da",
- "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f",
- "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c",
- "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"
- ],
- "version": "==1.0.4"
+ "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164",
+ "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b",
+ "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c",
+ "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf",
+ "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd",
+ "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d",
+ "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c",
+ "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a",
+ "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e",
+ "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd",
+ "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025",
+ "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5",
+ "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705",
+ "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a",
+ "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d",
+ "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb",
+ "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11",
+ "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f",
+ "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c",
+ "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d",
+ "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea",
+ "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba",
+ "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87",
+ "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a",
+ "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c",
+ "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080",
+ "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198",
+ "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9",
+ "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a",
+ "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b",
+ "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f",
+ "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437",
+ "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f",
+ "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7",
+ "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2",
+ "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0",
+ "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48",
+ "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898",
+ "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0",
+ "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57",
+ "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8",
+ "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282",
+ "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1",
+ "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82",
+ "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc",
+ "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb",
+ "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6",
+ "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7",
+ "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9",
+ "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c",
+ "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1",
+ "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed",
+ "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c",
+ "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c",
+ "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77",
+ "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81",
+ "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a",
+ "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3",
+ "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086",
+ "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9",
+ "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f",
+ "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b",
+ "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"
+ ],
+ "version": "==1.0.5"
},
"nodeenv": {
"hashes": [
@@ -742,170 +903,225 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'",
"version": "==1.7.0"
},
+ "numpy": {
+ "hashes": [
+ "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187",
+ "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812",
+ "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7",
+ "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4",
+ "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6",
+ "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0",
+ "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4",
+ "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570",
+ "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4",
+ "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f",
+ "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80",
+ "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289",
+ "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385",
+ "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078",
+ "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c",
+ "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463",
+ "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3",
+ "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950",
+ "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155",
+ "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7",
+ "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c",
+ "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096",
+ "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17",
+ "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf",
+ "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4",
+ "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02",
+ "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c",
+ "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"
+ ],
+ "index": "pypi",
+ "version": "==1.24.3"
+ },
"oauthlib": {
"hashes": [
- "sha256:1565237372795bf6ee3e5aba5e2a85bd5a65d0e2aa5c628b9a97b7d7a0da3721",
- "sha256:88e912ca1ad915e1dcc1c06fc9259d19de8deacd6fd17cc2df266decc2e49066"
+ "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca",
+ "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"
],
"markers": "python_version >= '3.6'",
- "version": "==3.2.1"
+ "version": "==3.2.2"
},
"openpyxl": {
"hashes": [
- "sha256:0ab6d25d01799f97a9464630abacbb34aafecdcaa0ef3cba6d6b3499867d0355",
- "sha256:e47805627aebcf860edb4edf7987b1309c1b3632f3750538ed962bbcc3bd7449"
+ "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184",
+ "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5"
],
"markers": "python_version >= '3.6'",
- "version": "==3.0.10"
+ "version": "==3.1.2"
},
"packaging": {
"hashes": [
- "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
- "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
+ "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
+ "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
- "markers": "python_version >= '3.6'",
- "version": "==21.3"
+ "markers": "python_version >= '3.7'",
+ "version": "==23.1"
+ },
+ "pandas": {
+ "hashes": [
+ "sha256:0778ab54c8f399d83d98ffb674d11ec716449956bc6f6821891ab835848687f2",
+ "sha256:24472cfc7ced511ac90608728b88312be56edc8f19b9ed885a7d2e47ffaf69c0",
+ "sha256:2d1d138848dd71b37e3cbe7cd952ff84e2ab04d8988972166e18567dcc811245",
+ "sha256:3bb9d840bf15656805f6a3d87eea9dcb7efdf1314a82adcf7f00b820427c5570",
+ "sha256:425705cee8be54db2504e8dd2a730684790b15e5904b750c367611ede49098ab",
+ "sha256:4f3320bb55f34af4193020158ef8118ee0fb9aec7cc47d2084dbfdd868a0a24f",
+ "sha256:4ffb14f50c74ee541610668137830bb93e9dfa319b1bef2cedf2814cd5ac9c70",
+ "sha256:52c858de9e9fc422d25e67e1592a6e6135d7bcf9a19fcaf4d0831a0be496bf21",
+ "sha256:57c34b79c13249505e850d0377b722961b99140f81dafbe6f19ef10239f6284a",
+ "sha256:6ded51f7e3dd9b4f8b87f2ceb7bd1a8df2491f7ee72f7074c6927a512607199e",
+ "sha256:70db5c278bbec0306d32bf78751ff56b9594c05a5098386f6c8a563659124f91",
+ "sha256:78425ca12314b23356c28b16765639db10ebb7d8983f705d6759ff7fe41357fa",
+ "sha256:8318de0f886e4dcb8f9f36e45a3d6a6c3d1cfdc508354da85e739090f0222991",
+ "sha256:8f987ec26e96a8490909bc5d98c514147236e49830cba7df8690f6087c12bbae",
+ "sha256:9253edfd015520ce77a9343eb7097429479c039cd3ebe81d7810ea11b4b24695",
+ "sha256:977326039bd1ded620001a1889e2ed4798460a6bc5a24fbaebb5f07a41c32a55",
+ "sha256:a4f789b7c012a608c08cda4ff0872fd979cb18907a37982abe884e6f529b8793",
+ "sha256:b3ba8f5dd470d8bfbc4259829589f4a32881151c49e36384d9eb982b35a12020",
+ "sha256:b5337c87c4e963f97becb1217965b6b75c6fe5f54c4cf09b9a5ac52fc0bd03d3",
+ "sha256:bbb2c5e94d6aa4e632646a3bacd05c2a871c3aa3e85c9bec9be99cb1267279f2",
+ "sha256:c24c7d12d033a372a9daf9ff2c80f8b0af6f98d14664dbb0a4f6a029094928a7",
+ "sha256:cda9789e61b44463c1c4fe17ef755de77bcd13b09ba31c940d20f193d63a5dc8",
+ "sha256:d08e41d96bc4de6f500afe80936c68fce6099d5a434e2af7c7fd8e7c72a3265d",
+ "sha256:d93b7fcfd9f3328072b250d6d001dcfeec5d3bb66c1b9c8941e109a46c0c01a8",
+ "sha256:fcd471c9d9f60926ab2f15c6c29164112f458acb42280365fbefa542d0c2fc74"
+ ],
+ "index": "pypi",
+ "version": "==2.0.0"
},
"phonenumbers": {
"hashes": [
- "sha256:80a7422cf0999a6f9b7a2e6cfbdbbfcc56ab5b75414dc3b805bbec91276b64a3",
- "sha256:82a4f226c930d02dcdf6d4b29e4cfd8678991fe65c2efd5fdd143557186f0868"
+ "sha256:421b69fd6d6650372000a6c47ab5b5c5d7b438b33f7b317739e728eff1ec1886",
+ "sha256:fe071b8324473e72a54b52e602d059c15b999ec9900fff9e42c01b422aeca662"
],
"index": "pypi",
- "version": "==8.12.56"
+ "version": "==8.13.10"
},
"pillow": {
"hashes": [
- "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927",
- "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14",
- "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc",
- "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58",
- "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60",
- "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76",
- "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c",
- "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac",
- "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490",
- "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1",
- "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f",
- "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d",
- "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f",
- "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069",
- "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402",
- "sha256:336b9036127eab855beec9662ac3ea13a4544a523ae273cbf108b228ecac8437",
- "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885",
- "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e",
- "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be",
- "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff",
- "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da",
- "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004",
- "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f",
- "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20",
- "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d",
- "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c",
- "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544",
- "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3",
- "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04",
- "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c",
- "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5",
- "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4",
- "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb",
- "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4",
- "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c",
- "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467",
- "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e",
- "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421",
- "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b",
- "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8",
- "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb",
- "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3",
- "sha256:adabc0bce035467fb537ef3e5e74f2847c8af217ee0be0455d4fec8adc0462fc",
- "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf",
- "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1",
- "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a",
- "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28",
- "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0",
- "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1",
- "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8",
- "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd",
- "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4",
- "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8",
- "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f",
- "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013",
- "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59",
- "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc",
- "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"
+ "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1",
+ "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba",
+ "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a",
+ "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799",
+ "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51",
+ "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb",
+ "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5",
+ "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270",
+ "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6",
+ "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47",
+ "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf",
+ "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e",
+ "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b",
+ "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66",
+ "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865",
+ "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec",
+ "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c",
+ "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1",
+ "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38",
+ "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906",
+ "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705",
+ "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef",
+ "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc",
+ "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f",
+ "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf",
+ "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392",
+ "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d",
+ "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe",
+ "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32",
+ "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5",
+ "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7",
+ "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44",
+ "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d",
+ "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3",
+ "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625",
+ "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e",
+ "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829",
+ "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089",
+ "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3",
+ "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78",
+ "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96",
+ "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964",
+ "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597",
+ "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99",
+ "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a",
+ "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140",
+ "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7",
+ "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16",
+ "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903",
+ "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1",
+ "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296",
+ "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572",
+ "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115",
+ "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a",
+ "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd",
+ "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4",
+ "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1",
+ "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb",
+ "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa",
+ "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a",
+ "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569",
+ "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c",
+ "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf",
+ "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082",
+ "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062",
+ "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"
],
"index": "pypi",
- "version": "==9.2.0"
+ "version": "==9.5.0"
},
"platformdirs": {
"hashes": [
- "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788",
- "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"
+ "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08",
+ "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e"
],
"markers": "python_version >= '3.7'",
- "version": "==2.5.2"
+ "version": "==3.2.0"
},
"pre-commit": {
"hashes": [
- "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7",
- "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"
+ "sha256:0b4210aea813fe81144e87c5a291f09ea66f199f367fa1df41b55e1d26e1e2b4",
+ "sha256:5b808fcbda4afbccf6d6633a56663fed35b6c2bc08096fd3d47ce197ac351d9d"
],
"index": "pypi",
- "version": "==2.20.0"
+ "version": "==3.2.2"
},
"psycopg2": {
"hashes": [
- "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c",
- "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf",
- "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362",
- "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7",
- "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461",
- "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126",
- "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981",
- "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56",
- "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305",
- "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2",
- "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"
+ "sha256:11aca705ec888e4f4cea97289a0bf0f22a067a32614f6ef64fcf7b8bfbc53744",
+ "sha256:1861a53a6a0fd248e42ea37c957d36950da00266378746588eab4f4b5649e95f",
+ "sha256:2362ee4d07ac85ff0ad93e22c693d0f37ff63e28f0615a16b6635a645f4b9214",
+ "sha256:36c941a767341d11549c0fbdbb2bf5be2eda4caf87f65dfcd7d146828bd27f39",
+ "sha256:53f4ad0a3988f983e9b49a5d9765d663bbe84f508ed655affdb810af9d0972ad",
+ "sha256:869776630c04f335d4124f120b7fb377fe44b0a7645ab3c34b4ba42516951889",
+ "sha256:a8ad4a47f42aa6aec8d061fdae21eaed8d864d4bb0f0cade5ad32ca16fcd6258",
+ "sha256:b81fcb9ecfc584f661b71c889edeae70bae30d3ef74fa0ca388ecda50b1222b7",
+ "sha256:d24ead3716a7d093b90b27b3d73459fe8cd90fd7065cf43b3c40966221d8c394",
+ "sha256:ded2faa2e6dfb430af7713d87ab4abbfc764d8d7fb73eafe96a24155f906ebf5",
+ "sha256:f15158418fd826831b28585e2ab48ed8df2d0d98f502a2b4fe619e7d5ca29011",
+ "sha256:f75001a1cbbe523e00b0ef896a5a1ada2da93ccd752b7636db5a99bc57c44494",
+ "sha256:f7a7a5ee78ba7dc74265ba69e010ae89dae635eea0e97b055fb641a01a31d2b1"
],
"index": "pypi",
- "version": "==2.9.3"
+ "version": "==2.9.6"
},
"pyasn1": {
"hashes": [
- "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
- "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
- "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
- "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
- "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
- "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
- "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
- "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
- "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
- "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
- "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
- "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
- "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
- ],
- "version": "==0.4.8"
+ "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57",
+ "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+ "version": "==0.5.0"
},
"pyasn1-modules": {
"hashes": [
- "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
- "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
- "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
- "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
- "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
- "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
- "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
- "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
- "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
- "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
- "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
- "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
- "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"
- ],
- "version": "==0.2.8"
+ "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c",
+ "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+ "version": "==0.3.0"
},
"pycparser": {
"hashes": [
@@ -917,27 +1133,26 @@
},
"pyjwt": {
"hashes": [
- "sha256:8d82e7087868e94dd8d7d418e5088ce64f7daab4b36db654cbaedb46f9d1ca80",
- "sha256:e77ab89480905d86998442ac5788f35333fa85f65047a534adc38edf3c88fc3b"
+ "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd",
+ "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"
],
"markers": "python_version >= '3.7'",
- "version": "==2.5.0"
+ "version": "==2.6.0"
},
"pyopenssl": {
"hashes": [
- "sha256:7a83b7b272dd595222d672f5ce29aa030f1fb837630ef229f62e72e395ce8968",
- "sha256:b28437c9773bb6c6958628cf9c3bebe585de661dba6f63df17111966363dd15e"
+ "sha256:841498b9bec61623b1b6c47ebbc02367c07d60e0e195f19790817f10cc8db0b7",
+ "sha256:9e0c526404a210df9d2b18cd33364beadb0dc858a739b885677bc65e105d4a4c"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==22.1.0"
+ "version": "==23.1.1"
},
- "pyparsing": {
+ "pypng": {
"hashes": [
- "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
- "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
+ "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c",
+ "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"
],
- "markers": "python_full_version >= '3.6.8'",
- "version": "==3.0.9"
+ "version": "==0.20220715.0"
},
"python-dateutil": {
"hashes": [
@@ -949,10 +1164,10 @@
},
"python-dotenv": {
"hashes": [
- "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5",
- "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"
+ "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba",
+ "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"
],
- "version": "==0.21.0"
+ "version": "==1.0.0"
},
"python3-openid": {
"hashes": [
@@ -964,10 +1179,10 @@
},
"pytz": {
"hashes": [
- "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197",
- "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"
+ "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588",
+ "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"
],
- "version": "==2022.2.1"
+ "version": "==2023.3"
},
"pyyaml": {
"hashes": [
@@ -1017,26 +1232,27 @@
},
"qrcode": {
"hashes": [
- "sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578"
+ "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a",
+ "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845"
],
"index": "pypi",
- "version": "==7.3.1"
+ "version": "==7.4.2"
},
"redis": {
"hashes": [
- "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54",
- "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"
+ "sha256:2c19e6767c474f2e85167909061d525ed65bea9301c0770bb151e041b7ac89a2",
+ "sha256:73ec35da4da267d6847e47f68730fdd5f62e2ca69e3ef5885c6a78a9374c3893"
],
- "markers": "python_version >= '3.6'",
- "version": "==4.3.4"
+ "markers": "python_version >= '3.7'",
+ "version": "==4.5.4"
},
"requests": {
"hashes": [
- "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983",
- "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"
+ "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa",
+ "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"
],
- "markers": "python_version >= '3.7' and python_version < '4'",
- "version": "==2.28.1"
+ "markers": "python_version >= '3.7' and python_version < '4.0'",
+ "version": "==2.28.2"
},
"requests-oauthlib": {
"hashes": [
@@ -1056,11 +1272,11 @@
},
"sentry-sdk": {
"hashes": [
- "sha256:d6c71d2f85710b66822adaa954af7912bab135d6c85febd5b0f3dfd4ab37e181",
- "sha256:ef925b5338625448645a778428d8f22a3d17de8b28cc8e6fba60b93393ad86fe"
+ "sha256:0ad6bbbe78057b8031a07de7aca6d2a83234e51adc4d436eaf8d8c697184db71",
+ "sha256:a3410381ae769a436c0852cce140a5e5e49f566a07fb7c2ab445af1302f6ad89"
],
"index": "pypi",
- "version": "==1.9.9"
+ "version": "==1.20.0"
},
"service-identity": {
"hashes": [
@@ -1071,11 +1287,11 @@
},
"setuptools": {
"hashes": [
- "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012",
- "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"
+ "sha256:6f0839fbdb7e3cfef1fc38d7954f5c1c26bf4eebb155a55c9bf8faf997b9fb67",
+ "sha256:bb16732e8eb928922eabaa022f881ae2b7cdcfaf9993ef1f5e841a96d32b8e0c"
],
"markers": "python_version >= '3.7'",
- "version": "==65.4.1"
+ "version": "==67.7.1"
},
"six": {
"hashes": [
@@ -1095,35 +1311,35 @@
},
"social-auth-app-django": {
"hashes": [
- "sha256:52241a25445a010ab1c108bafff21fc5522d5c8cd0d48a92c39c7371824b065d",
- "sha256:b6e3132ce087cdd6e1707aeb1b588be41d318408fcf6395435da0bc6fe9a9795"
+ "sha256:0347ca4cd23ea9d15a665da9d22950552fb66b95600e6c2ebae38ca883b3a4ed",
+ "sha256:4a5dae406f3874b4003708ff120c02cb1a4c8eeead56cd163646347309fcd0f8"
],
"index": "pypi",
- "version": "==5.0.0"
+ "version": "==5.2.0"
},
"social-auth-core": {
"hashes": [
- "sha256:1e3440d104f743b02dfe258c9d4dba5b4065abf24b2f7eb362b47054d21797df",
- "sha256:4686f0e43cf12954216875a32e944847bb1dc69e7cd9573d16a9003bb05ca477"
+ "sha256:9791d7c7aee2ac8517fe7a2ea2f942a8a5492b3a4ccb44a9b0dacc87d182f2aa",
+ "sha256:ea7a19c46b791b767e95f467881b53c5fd0d1efb40048d9ed3dbc46daa05c954"
],
"markers": "python_version >= '3.6'",
- "version": "==4.3.0"
+ "version": "==4.4.2"
},
"soupsieve": {
"hashes": [
- "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759",
- "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"
+ "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8",
+ "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"
],
- "markers": "python_version >= '3.6'",
- "version": "==2.3.2.post1"
+ "markers": "python_version >= '3.7'",
+ "version": "==2.4.1"
},
"sqlparse": {
"hashes": [
- "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34",
- "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"
+ "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3",
+ "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"
],
- "markers": "python_version >= '3.5'",
- "version": "==0.4.3"
+ "markers": "python_full_version >= '3.5.0'",
+ "version": "==0.4.4"
},
"tatsu": {
"hashes": [
@@ -1141,40 +1357,40 @@
"index": "pypi",
"version": "==1.7.0"
},
- "toml": {
- "hashes": [
- "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
- "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
- ],
- "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==0.10.2"
- },
"twisted": {
"extras": [
"tls"
],
"hashes": [
- "sha256:8d4718d1e48dcc28933f8beb48dc71cfe77a125e37ad1eb7a3d0acc49baf6c99",
- "sha256:e5b60de39f2d1da153fbe1874d885fe3fcbdb21fcc446fa759a53e8fc3513bed"
+ "sha256:32acbd40a94f5f46e7b42c109bfae2b302250945561783a8b7a059048f2d4d31",
+ "sha256:86c55f712cc5ab6f6d64e02503352464f0400f66d4f079096d744080afcccbd0"
],
"markers": "python_full_version >= '3.7.1'",
- "version": "==22.8.0"
+ "version": "==22.10.0"
},
"txaio": {
"hashes": [
- "sha256:2e4582b70f04b2345908254684a984206c0d9b50e3074a24a4c55aba21d24d01",
- "sha256:41223af4a9d5726e645a8ee82480f413e5e300dd257db94bc38ae12ea48fb2e5"
+ "sha256:aaea42f8aad50e0ecfb976130ada140797e9dcb85fad2cf72b0f37f8cefcb490",
+ "sha256:f9a9216e976e5e3246dfd112ad7ad55ca915606b60b84a757ac769bd404ff704"
],
- "markers": "python_version >= '3.6'",
- "version": "==22.2.1"
+ "markers": "python_version >= '3.7'",
+ "version": "==23.1.1"
},
"typing-extensions": {
"hashes": [
- "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
- "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
+ "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
+ "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
],
"markers": "python_version >= '3.7'",
- "version": "==4.3.0"
+ "version": "==4.5.0"
+ },
+ "tzdata": {
+ "hashes": [
+ "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a",
+ "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"
+ ],
+ "markers": "python_version >= '2'",
+ "version": "==2023.3"
},
"unittest-xml-reporting": {
"hashes": [
@@ -1194,38 +1410,38 @@
},
"uritools": {
"hashes": [
- "sha256:420d94c1ff4bf90c678fca9c17b8314243bbcaa992c400a95e327f7f622e1edf",
- "sha256:9a5a1495c55072093216f79931ca45fd81b59208aa64caae50ab68333514f97e"
+ "sha256:d122d394ed6e6e15ac0fddba6a5b19e9fa204e7797507815cbfb0e1455ac0475",
+ "sha256:efc5c3a6de05404850685a8d3f34da8476b56aa3516fbf8eff5c8704c7a2826f"
],
"markers": "python_version ~= '3.7'",
- "version": "==4.0.0"
+ "version": "==4.0.1"
},
"urlextract": {
"hashes": [
- "sha256:574f0d8c562d377336a5154840c7e4eecf54c102a538971c897f7313075a8887",
- "sha256:c22a9645cae1e1390cc512fd4f4d1f37b72538eecfdc3365905a2e616e7b6b88"
+ "sha256:3573f6b812814efe06ca46e91e82d984edaa3cd07daaaaa296a467ad9881a037",
+ "sha256:98b38aca4a555116e8b46e5a134b9e4e54e351b8e37169d2857730d1d0ce42c7"
],
"index": "pypi",
- "version": "==1.6.0"
+ "version": "==1.8.0"
},
"urllib3": {
"hashes": [
- "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e",
- "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"
+ "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305",
+ "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"
],
"markers": "python_version >= '3.6'",
- "version": "==1.26.12"
+ "version": "==1.26.15"
},
"uvicorn": {
"extras": [
"standard"
],
"hashes": [
- "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af",
- "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"
+ "sha256:0fac9cb342ba099e0d582966005f3fdba5b0290579fed4a6266dc702ca7bb032",
+ "sha256:e47cac98a6da10cd41e6fd036d472c6f58ede6c5dbee3dbee3ef7a100ed97742"
],
"index": "pypi",
- "version": "==0.18.3"
+ "version": "==0.21.1"
},
"uvloop": {
"hashes": [
@@ -1265,41 +1481,45 @@
},
"uwsgi": {
"hashes": [
- "sha256:88ab9867d8973d8ae84719cf233b7dafc54326fcaec89683c3f9f77c002cdff9"
+ "sha256:35a30d83791329429bc04fe44183ce4ab512fcf6968070a7bfba42fc5a0552a9"
],
"markers": "sys_platform == 'linux'",
- "version": "==2.0.20"
+ "version": "==2.0.21"
},
"virtualenv": {
"hashes": [
- "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da",
- "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"
+ "sha256:278753c47aaef1a0f14e6db8a4c5e1e040e90aea654d0fc1dc7e0d8a42616cc3",
+ "sha256:48fd3b907b5149c5aab7c23d9790bea4cac6bc6b150af8635febc4cfeab1275a"
],
- "markers": "python_version >= '3.6'",
- "version": "==20.16.5"
+ "markers": "python_version >= '3.7'",
+ "version": "==20.22.0"
},
"watchfiles": {
"hashes": [
- "sha256:00e5f307a58752ec1478eeb738863544bde21cc7a2728bd1c216060406bde9c1",
- "sha256:1dd1e3181ad5d83ca35e9147c72e24f39437fcdf570c9cdc532016399fb62957",
- "sha256:204950f1d6083539af5c8b7d4f5f8039c3ce36fa692da12d9743448f3199cb15",
- "sha256:4056398d8f6d4972fe0918707b59d4cb84470c91d3c37f0e11e5a66c2a598760",
- "sha256:539bcdb55a487126776c9d8c011094214d1df3f9a2321a6c0b1583197309405a",
- "sha256:53a2faeb121bc51bb6b960984f46901227e2e2475acc5a8d4c905a600436752d",
- "sha256:58dc3140dcf02a8aa76464a77a093016f10e89306fec21a4814922a64f3e8b9f",
- "sha256:6a3d6c699f3ce238dfa90bcef501f331a69b0d9b076f14459ed8eab26ba2f4cf",
- "sha256:92675f379a9d5adbc6a52179f3e39aa56944c6eecb80384608fff2ed2619103a",
- "sha256:a53cb6c06e5c1f216c792fbb432ce315239d432cb8b68d508547100939ec0399",
- "sha256:a7f4271af86569bdbf131dd5c7c121c45d0ed194f3c88b88326e48a3b6a2db12",
- "sha256:ad2bdcae4c0f07ca6c090f5a2c30188cc6edba011b45e7c96eb1896648092367",
- "sha256:adcf15ecc2182ea9d2358c1a8c2b53203c3909484918776929b7bbe205522c0e",
- "sha256:ae7c57ef920589a40270d5ef3216d693f4e6f8864d8fc8b6cb7885ca98ad2a61",
- "sha256:afd35a1bd3b9e68efe384ae7538481ae725597feb66f56f4bd23ecdbda726da0",
- "sha256:b5c334cd3bc88aa4a8a1e08ec9f702b63c947211275defdc2dd79dc037fcb500",
- "sha256:c7e1ffbd03cbcb46d1b7833e10e7d6b678ab083b4e4b80db06cfff5baca3c93f",
- "sha256:ffff3418dc753a2aed2d00200a4daeaac295c40458f8012836a65555f288be8b"
- ],
- "version": "==0.17.0"
+ "sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911",
+ "sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda",
+ "sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154",
+ "sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af",
+ "sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d",
+ "sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c",
+ "sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48",
+ "sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c",
+ "sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545",
+ "sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e",
+ "sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120",
+ "sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7",
+ "sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8",
+ "sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc",
+ "sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056",
+ "sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193",
+ "sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3",
+ "sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf",
+ "sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79",
+ "sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1",
+ "sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b",
+ "sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"
+ ],
+ "version": "==0.19.0"
},
"webencodings": {
"hashes": [
@@ -1310,183 +1530,195 @@
},
"websockets": {
"hashes": [
- "sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af",
- "sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c",
- "sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76",
- "sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47",
- "sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69",
- "sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079",
- "sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c",
- "sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55",
- "sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02",
- "sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559",
- "sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3",
- "sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e",
- "sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978",
- "sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98",
- "sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae",
- "sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755",
- "sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d",
- "sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991",
- "sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1",
- "sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680",
- "sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247",
- "sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f",
- "sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2",
- "sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7",
- "sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4",
- "sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667",
- "sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb",
- "sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094",
- "sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36",
- "sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79",
- "sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500",
- "sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e",
- "sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582",
- "sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442",
- "sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd",
- "sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6",
- "sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731",
- "sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4",
- "sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d",
- "sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8",
- "sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f",
- "sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677",
- "sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8",
- "sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9",
- "sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e",
- "sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b",
- "sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916",
- "sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4"
- ],
- "version": "==10.3"
+ "sha256:0fb4480556825e4e6bf2eebdbeb130d9474c62705100c90e59f2f56459ddab42",
+ "sha256:13bd5bebcd16a4b5e403061b8b9dcc5c77e7a71e3c57e072d8dff23e33f70fba",
+ "sha256:143782041e95b63083b02107f31cda999f392903ae331de1307441f3a4557d51",
+ "sha256:1b52def56d2a26e0e9c464f90cadb7e628e04f67b0ff3a76a4d9a18dfc35e3dd",
+ "sha256:1df2413266bf48430ef2a752c49b93086c6bf192d708e4a9920544c74cd2baa6",
+ "sha256:2174a75d579d811279855df5824676d851a69f52852edb0e7551e0eeac6f59a4",
+ "sha256:220d5b93764dd70d7617f1663da64256df7e7ea31fc66bc52c0e3750ee134ae3",
+ "sha256:232b6ba974f5d09b1b747ac232f3a3d8f86de401d7b565e837cc86988edf37ac",
+ "sha256:25aae96c1060e85836552a113495db6d857400288161299d77b7b20f2ac569f2",
+ "sha256:25e265686ea385f22a00cc2b719b880797cd1bb53b46dbde969e554fb458bfde",
+ "sha256:2abeeae63154b7f63d9f764685b2d299e9141171b8b896688bd8baec6b3e2303",
+ "sha256:2acdc82099999e44fa7bd8c886f03c70a22b1d53ae74252f389be30d64fd6004",
+ "sha256:2eb042734e710d39e9bc58deab23a65bd2750e161436101488f8af92f183c239",
+ "sha256:3178d965ec204773ab67985a09f5696ca6c3869afeed0bb51703ea404a24e975",
+ "sha256:320ddceefd2364d4afe6576195201a3632a6f2e6d207b0c01333e965b22dbc84",
+ "sha256:34a6f8996964ccaa40da42ee36aa1572adcb1e213665e24aa2f1037da6080909",
+ "sha256:3565a8f8c7bdde7c29ebe46146bd191290413ee6f8e94cf350609720c075b0a1",
+ "sha256:392d409178db1e46d1055e51cc850136d302434e12d412a555e5291ab810f622",
+ "sha256:3a09cce3dacb6ad638fdfa3154d9e54a98efe7c8f68f000e55ca9c716496ca67",
+ "sha256:3a2100b02d1aaf66dc48ff1b2a72f34f6ebc575a02bc0350cc8e9fbb35940166",
+ "sha256:3b87cd302f08ea9e74fdc080470eddbed1e165113c1823fb3ee6328bc40ca1d3",
+ "sha256:3e79065ff6549dd3c765e7916067e12a9c91df2affea0ac51bcd302aaf7ad207",
+ "sha256:3ffe251a31f37e65b9b9aca5d2d67fd091c234e530f13d9dce4a67959d5a3fba",
+ "sha256:46388a050d9e40316e58a3f0838c63caacb72f94129eb621a659a6e49bad27ce",
+ "sha256:46dda4bc2030c335abe192b94e98686615f9274f6b56f32f2dd661fb303d9d12",
+ "sha256:4c54086b2d2aec3c3cb887ad97e9c02c6be9f1d48381c7419a4aa932d31661e4",
+ "sha256:5004c087d17251938a52cce21b3dbdabeecbbe432ce3f5bbbf15d8692c36eac9",
+ "sha256:502683c5dedfc94b9f0f6790efb26aa0591526e8403ad443dce922cd6c0ec83b",
+ "sha256:518ed6782d9916c5721ebd61bb7651d244178b74399028302c8617d0620af291",
+ "sha256:580cc95c58118f8c39106be71e24d0b7e1ad11a155f40a2ee687f99b3e5e432e",
+ "sha256:58477b041099bb504e1a5ddd8aa86302ed1d5c6995bdd3db2b3084ef0135d277",
+ "sha256:5875f623a10b9ba154cb61967f940ab469039f0b5e61c80dd153a65f024d9fb7",
+ "sha256:5c7de298371d913824f71b30f7685bb07ad13969c79679cca5b1f7f94fec012f",
+ "sha256:634239bc844131863762865b75211a913c536817c0da27f691400d49d256df1d",
+ "sha256:6d872c972c87c393e6a49c1afbdc596432df8c06d0ff7cd05aa18e885e7cfb7c",
+ "sha256:752fbf420c71416fb1472fec1b4cb8631c1aa2be7149e0a5ba7e5771d75d2bb9",
+ "sha256:7742cd4524622cc7aa71734b51294644492a961243c4fe67874971c4d3045982",
+ "sha256:808b8a33c961bbd6d33c55908f7c137569b09ea7dd024bce969969aa04ecf07c",
+ "sha256:87c69f50281126dcdaccd64d951fb57fbce272578d24efc59bce72cf264725d0",
+ "sha256:8df63dcd955eb6b2e371d95aacf8b7c535e482192cff1b6ce927d8f43fb4f552",
+ "sha256:8f24cd758cbe1607a91b720537685b64e4d39415649cac9177cd1257317cf30c",
+ "sha256:8f392587eb2767afa8a34e909f2fec779f90b630622adc95d8b5e26ea8823cb8",
+ "sha256:954eb789c960fa5daaed3cfe336abc066941a5d456ff6be8f0e03dd89886bb4c",
+ "sha256:955fcdb304833df2e172ce2492b7b47b4aab5dcc035a10e093d911a1916f2c87",
+ "sha256:95c09427c1c57206fe04277bf871b396476d5a8857fa1b99703283ee497c7a5d",
+ "sha256:a4fe2442091ff71dee0769a10449420fd5d3b606c590f78dd2b97d94b7455640",
+ "sha256:aa7b33c1fb2f7b7b9820f93a5d61ffd47f5a91711bc5fa4583bbe0c0601ec0b2",
+ "sha256:adf6385f677ed2e0b021845b36f55c43f171dab3a9ee0ace94da67302f1bc364",
+ "sha256:b1a69701eb98ed83dd099de4a686dc892c413d974fa31602bc00aca7cb988ac9",
+ "sha256:b2a573c8d71b7af937852b61e7ccb37151d719974146b5dc734aad350ef55a02",
+ "sha256:b444366b605d2885f0034dd889faf91b4b47668dd125591e2c64bfde611ac7e1",
+ "sha256:b985ba2b9e972cf99ddffc07df1a314b893095f62c75bc7c5354a9c4647c6503",
+ "sha256:c78ca3037a954a4209b9f900e0eabbc471fb4ebe96914016281df2c974a93e3e",
+ "sha256:ca9b2dced5cbbc5094678cc1ec62160f7b0fe4defd601cd28a36fde7ee71bbb5",
+ "sha256:cb46d2c7631b2e6f10f7c8bac7854f7c5e5288f024f1c137d4633c79ead1e3c0",
+ "sha256:ce69f5c742eefd039dce8622e99d811ef2135b69d10f9aa79fbf2fdcc1e56cd7",
+ "sha256:cf45d273202b0c1cec0f03a7972c655b93611f2e996669667414557230a87b88",
+ "sha256:d1881518b488a920434a271a6e8a5c9481a67c4f6352ebbdd249b789c0467ddc",
+ "sha256:d3cc3e48b6c9f7df8c3798004b9c4b92abca09eeea5e1b0a39698f05b7a33b9d",
+ "sha256:d6b2bfa1d884c254b841b0ff79373b6b80779088df6704f034858e4d705a4802",
+ "sha256:d70a438ef2a22a581d65ad7648e949d4ccd20e3c8ed7a90bbc46df4e60320891",
+ "sha256:daa1e8ea47507555ed7a34f8b49398d33dff5b8548eae3de1dc0ef0607273a33",
+ "sha256:dca9708eea9f9ed300394d4775beb2667288e998eb6f542cdb6c02027430c599",
+ "sha256:dd906b0cdc417ea7a5f13bb3c6ca3b5fd563338dc596996cb0fdd7872d691c0a",
+ "sha256:e0eeeea3b01c97fd3b5049a46c908823f68b59bf0e18d79b231d8d6764bc81ee",
+ "sha256:e37a76ccd483a6457580077d43bc3dfe1fd784ecb2151fcb9d1c73f424deaeba",
+ "sha256:e8b967a4849db6b567dec3f7dd5d97b15ce653e3497b8ce0814e470d5e074750",
+ "sha256:ec00401846569aaf018700249996143f567d50050c5b7b650148989f956547af",
+ "sha256:ede13a6998ba2568b21825809d96e69a38dc43184bdeebbde3699c8baa21d015",
+ "sha256:f97e03d4d5a4f0dca739ea274be9092822f7430b77d25aa02da6775e490f6846"
+ ],
+ "version": "==11.0.2"
},
"wrapt": {
"hashes": [
- "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3",
- "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b",
- "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4",
- "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2",
- "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656",
- "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3",
- "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff",
- "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310",
- "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a",
- "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57",
- "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069",
- "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383",
- "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe",
- "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87",
- "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d",
- "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b",
- "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907",
- "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f",
- "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0",
- "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28",
- "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1",
- "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853",
- "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc",
- "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3",
- "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3",
- "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164",
- "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1",
- "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c",
- "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1",
- "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7",
- "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1",
- "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320",
- "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed",
- "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1",
- "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248",
- "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c",
- "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456",
- "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77",
- "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef",
- "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1",
- "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7",
- "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86",
- "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4",
- "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d",
- "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d",
- "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8",
- "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5",
- "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471",
- "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00",
- "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68",
- "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3",
- "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d",
- "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735",
- "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d",
- "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569",
- "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7",
- "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59",
- "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5",
- "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb",
- "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b",
- "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f",
- "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462",
- "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015",
- "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"
+ "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0",
+ "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420",
+ "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a",
+ "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c",
+ "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079",
+ "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923",
+ "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f",
+ "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1",
+ "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8",
+ "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86",
+ "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0",
+ "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364",
+ "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e",
+ "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c",
+ "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e",
+ "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c",
+ "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727",
+ "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff",
+ "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e",
+ "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29",
+ "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7",
+ "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72",
+ "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475",
+ "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a",
+ "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317",
+ "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2",
+ "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd",
+ "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640",
+ "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98",
+ "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248",
+ "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e",
+ "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d",
+ "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec",
+ "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1",
+ "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e",
+ "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9",
+ "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92",
+ "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb",
+ "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094",
+ "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46",
+ "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29",
+ "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd",
+ "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705",
+ "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8",
+ "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975",
+ "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb",
+ "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e",
+ "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b",
+ "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418",
+ "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019",
+ "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1",
+ "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba",
+ "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6",
+ "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2",
+ "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3",
+ "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7",
+ "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752",
+ "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416",
+ "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f",
+ "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1",
+ "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc",
+ "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145",
+ "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee",
+ "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a",
+ "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7",
+ "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b",
+ "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653",
+ "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0",
+ "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90",
+ "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29",
+ "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6",
+ "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034",
+ "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09",
+ "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559",
+ "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==1.14.1"
+ "version": "==1.15.0"
},
"zope.interface": {
"hashes": [
- "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192",
- "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702",
- "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09",
- "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4",
- "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a",
- "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3",
- "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf",
- "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c",
- "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d",
- "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78",
- "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83",
- "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531",
- "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46",
- "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021",
- "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94",
- "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc",
- "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63",
- "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54",
- "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117",
- "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25",
- "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05",
- "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e",
- "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1",
- "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004",
- "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2",
- "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e",
- "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f",
- "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f",
- "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120",
- "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f",
- "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1",
- "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9",
- "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e",
- "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7",
- "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8",
- "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b",
- "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155",
- "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7",
- "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c",
- "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325",
- "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d",
- "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb",
- "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e",
- "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959",
- "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7",
- "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920",
- "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e",
- "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48",
- "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8",
- "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
- "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
+ "sha256:042f2381118b093714081fd82c98e3b189b68db38ee7d35b63c327c470ef8373",
+ "sha256:0ec9653825f837fbddc4e4b603d90269b501486c11800d7c761eee7ce46d1bbb",
+ "sha256:12175ca6b4db7621aedd7c30aa7cfa0a2d65ea3a0105393e05482d7a2d367446",
+ "sha256:1592f68ae11e557b9ff2bc96ac8fc30b187e77c45a3c9cd876e3368c53dc5ba8",
+ "sha256:23ac41d52fd15dd8be77e3257bc51bbb82469cf7f5e9a30b75e903e21439d16c",
+ "sha256:424d23b97fa1542d7be882eae0c0fc3d6827784105264a8169a26ce16db260d8",
+ "sha256:4407b1435572e3e1610797c9203ad2753666c62883b921318c5403fb7139dec2",
+ "sha256:48f4d38cf4b462e75fac78b6f11ad47b06b1c568eb59896db5b6ec1094eb467f",
+ "sha256:4c3d7dfd897a588ec27e391edbe3dd320a03684457470415870254e714126b1f",
+ "sha256:5171eb073474a5038321409a630904fd61f12dd1856dd7e9d19cd6fe092cbbc5",
+ "sha256:5a158846d0fca0a908c1afb281ddba88744d403f2550dc34405c3691769cdd85",
+ "sha256:6ee934f023f875ec2cfd2b05a937bd817efcc6c4c3f55c5778cbf78e58362ddc",
+ "sha256:790c1d9d8f9c92819c31ea660cd43c3d5451df1df61e2e814a6f99cebb292788",
+ "sha256:809fe3bf1a91393abc7e92d607976bbb8586512913a79f2bf7d7ec15bd8ea518",
+ "sha256:87b690bbee9876163210fd3f500ee59f5803e4a6607d1b1238833b8885ebd410",
+ "sha256:89086c9d3490a0f265a3c4b794037a84541ff5ffa28bb9c24cc9f66566968464",
+ "sha256:99856d6c98a326abbcc2363827e16bd6044f70f2ef42f453c0bd5440c4ce24e5",
+ "sha256:aab584725afd10c710b8f1e6e208dbee2d0ad009f57d674cb9d1b3964037275d",
+ "sha256:af169ba897692e9cd984a81cb0f02e46dacdc07d6cf9fd5c91e81f8efaf93d52",
+ "sha256:b39b8711578dcfd45fc0140993403b8a81e879ec25d53189f3faa1f006087dca",
+ "sha256:b3f543ae9d3408549a9900720f18c0194ac0fe810cecda2a584fd4dca2eb3bb8",
+ "sha256:d0583b75f2e70ec93f100931660328965bb9ff65ae54695fb3fa0a1255daa6f2",
+ "sha256:dfbbbf0809a3606046a41f8561c3eada9db811be94138f42d9135a5c47e75f6f",
+ "sha256:e538f2d4a6ffb6edfb303ce70ae7e88629ac6e5581870e66c306d9ad7b564a58",
+ "sha256:eba51599370c87088d8882ab74f637de0c4f04a6d08a312dce49368ba9ed5c2a",
+ "sha256:ee4b43f35f5dc15e1fec55ccb53c130adb1d11e8ad8263d68b1284b66a04190d",
+ "sha256:f2363e5fd81afb650085c6686f2ee3706975c54f331b426800b53531191fdf28",
+ "sha256:f299c020c6679cb389814a3b81200fe55d428012c5e76da7e722491f5d205990",
+ "sha256:f72f23bab1848edb7472309e9898603141644faec9fd57a823ea6b4d1c4c8995",
+ "sha256:fa90bac61c9dc3e1a563e5babb3fd2c0c1c80567e815442ddbe561eadc803b30"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==5.4.0"
+ "markers": "python_version >= '3.7'",
+ "version": "==6.0"
}
},
"develop": {
@@ -1499,19 +1731,19 @@
},
"asgiref": {
"hashes": [
- "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4",
- "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"
+ "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac",
+ "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"
],
"markers": "python_version >= '3.7'",
- "version": "==3.5.2"
+ "version": "==3.6.0"
},
"attrs": {
"hashes": [
- "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6",
- "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"
+ "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04",
+ "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"
],
- "markers": "python_version >= '3.5'",
- "version": "==22.1.0"
+ "markers": "python_version >= '3.7'",
+ "version": "==23.1.0"
},
"black": {
"hashes": [
@@ -1521,22 +1753,6 @@
"index": "pypi",
"version": "==19.10b0"
},
- "certifi": {
- "hashes": [
- "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14",
- "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==2022.9.24"
- },
- "charset-normalizer": {
- "hashes": [
- "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
- "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
- ],
- "markers": "python_full_version >= '3.6.0'",
- "version": "==2.1.1"
- },
"click": {
"hashes": [
"sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1",
@@ -1545,86 +1761,21 @@
"index": "pypi",
"version": "==8.0.4"
},
- "codecov": {
- "hashes": [
- "sha256:585dc217dc3d8185198ceb402f85d5cb5dbfa0c5f350a5abcdf9e347776a5b47",
- "sha256:782a8e5352f22593cbc5427a35320b99490eb24d9dcfa2155fd99d2b75cfb635",
- "sha256:a0da46bb5025426da895af90938def8ee12d37fcbcbbbc15b6dc64cf7ebc51c1"
- ],
- "index": "pypi",
- "version": "==2.1.12"
- },
- "coverage": {
- "hashes": [
- "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79",
- "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a",
- "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f",
- "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a",
- "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa",
- "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398",
- "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba",
- "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d",
- "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf",
- "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b",
- "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518",
- "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d",
- "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795",
- "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2",
- "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e",
- "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32",
- "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745",
- "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b",
- "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e",
- "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d",
- "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f",
- "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660",
- "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62",
- "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6",
- "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04",
- "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c",
- "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5",
- "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef",
- "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc",
- "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae",
- "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578",
- "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466",
- "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4",
- "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91",
- "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0",
- "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4",
- "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b",
- "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe",
- "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b",
- "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75",
- "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b",
- "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c",
- "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72",
- "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b",
- "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f",
- "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e",
- "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53",
- "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3",
- "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84",
- "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==6.5.0"
- },
"django": {
"hashes": [
- "sha256:115baf5049d5cf4163e43492cdc7139c306ed6d451e7d3571fe9612903903713",
- "sha256:f71934b1a822f14a86c9ac9634053689279cd04ae69cb6ade4a59471b886582b"
+ "sha256:08208dfe892eb64fff073ca743b3b952311104f939e7f6dae954fe72dcc533ba",
+ "sha256:4d492d9024c7b3dfababf49f94511ab6a58e2c9c3c7207786f1ba4eb77750706"
],
"index": "pypi",
- "version": "==3.2.15"
+ "version": "==3.2.18"
},
"django-debug-toolbar": {
"hashes": [
- "sha256:1e3acad24e3d351ba45c6fa2072e4164820307332a776b16c9f06d1f89503465",
- "sha256:80de23066b624d3970fd296cf02d61988e5d56c31aa0dc4a428970b46e2883a8"
+ "sha256:89619f6e0ea1057dca47bfc429ed99b237ef70074dabc065a7faa5f00e1459cf",
+ "sha256:bad339d68520652ddc1580c76f136fcbc3e020fd5ed96510a89a02ec81bb3fb1"
],
"index": "pypi",
- "version": "==3.7.0"
+ "version": "==4.0.0"
},
"django-extensions": {
"hashes": [
@@ -1636,118 +1787,119 @@
},
"flake8": {
"hashes": [
- "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db",
- "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"
+ "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7",
+ "sha256:93aa565ae2f0316b95bb57a354f2b2d55ee8508e1fe1cb13b77b9c195b4a2537",
+ "sha256:b27fd7faa8d90aaae763664a489012292990388e5d3604f383b290caefbbc922",
+ "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"
],
"index": "pypi",
- "version": "==5.0.4"
+ "version": "==5.0.3"
},
"flake8-isort": {
"hashes": [
- "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0",
- "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"
+ "sha256:537f453a660d7e903f602ecfa36136b140de279df58d02eb1b6a0c84e83c528c",
+ "sha256:aa0cac02a62c7739e370ce6b9c31743edac904bae4b157274511fc8a19c75bbc"
],
"index": "pypi",
- "version": "==4.2.0"
+ "version": "==6.0.0"
},
"flake8-quotes": {
"hashes": [
- "sha256:633adca6fb8a08131536af0d750b44d6985b9aba46f498871e21588c3e6f525a"
+ "sha256:6e26892b632dacba517bf27219c459a8396dcfac0f5e8204904c5a4ba9b480e1"
],
"index": "pypi",
- "version": "==3.3.1"
- },
- "idna": {
- "hashes": [
- "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
- "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
- ],
- "markers": "python_version >= '3.5'",
- "version": "==3.4"
+ "version": "==3.3.2"
},
"isort": {
"hashes": [
- "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7",
- "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"
+ "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504",
+ "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"
],
"index": "pypi",
- "version": "==5.10.1"
+ "version": "==5.12.0"
},
"lxml": {
"hashes": [
- "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318",
- "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c",
- "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b",
- "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000",
- "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73",
- "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d",
- "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb",
- "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8",
- "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2",
- "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345",
- "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94",
- "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e",
- "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b",
- "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc",
- "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a",
- "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9",
- "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc",
- "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387",
- "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb",
- "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7",
- "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4",
- "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97",
- "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67",
- "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627",
- "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7",
- "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd",
- "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3",
- "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7",
- "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130",
- "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b",
- "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036",
- "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785",
- "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca",
- "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91",
- "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc",
- "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536",
- "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391",
- "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3",
- "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d",
- "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21",
- "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3",
- "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d",
- "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29",
- "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715",
- "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed",
- "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25",
- "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c",
- "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785",
- "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837",
- "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4",
- "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b",
- "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2",
- "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067",
- "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448",
- "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d",
- "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2",
- "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc",
- "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c",
- "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5",
- "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84",
- "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8",
- "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf",
- "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7",
- "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e",
- "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb",
- "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b",
- "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3",
- "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad",
- "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8",
- "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"
+ "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7",
+ "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726",
+ "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03",
+ "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140",
+ "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a",
+ "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05",
+ "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03",
+ "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419",
+ "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4",
+ "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e",
+ "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67",
+ "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50",
+ "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894",
+ "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf",
+ "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947",
+ "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1",
+ "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd",
+ "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3",
+ "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92",
+ "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3",
+ "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457",
+ "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74",
+ "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf",
+ "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1",
+ "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4",
+ "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975",
+ "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5",
+ "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe",
+ "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7",
+ "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1",
+ "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2",
+ "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409",
+ "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f",
+ "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f",
+ "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5",
+ "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24",
+ "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e",
+ "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4",
+ "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a",
+ "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c",
+ "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de",
+ "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f",
+ "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b",
+ "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5",
+ "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7",
+ "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a",
+ "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c",
+ "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9",
+ "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e",
+ "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab",
+ "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941",
+ "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5",
+ "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45",
+ "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7",
+ "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892",
+ "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746",
+ "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c",
+ "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53",
+ "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe",
+ "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184",
+ "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38",
+ "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df",
+ "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9",
+ "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b",
+ "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2",
+ "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0",
+ "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda",
+ "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b",
+ "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5",
+ "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380",
+ "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33",
+ "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8",
+ "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1",
+ "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889",
+ "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9",
+ "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f",
+ "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==4.9.1"
+ "version": "==4.9.2"
},
"mccabe": {
"hashes": [
@@ -1759,11 +1911,11 @@
},
"pathspec": {
"hashes": [
- "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93",
- "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"
+ "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687",
+ "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"
],
"markers": "python_version >= '3.7'",
- "version": "==0.10.1"
+ "version": "==0.11.1"
},
"pycodestyle": {
"hashes": [
@@ -1781,122 +1933,79 @@
"markers": "python_version >= '3.6'",
"version": "==2.5.0"
},
- "pytz": {
- "hashes": [
- "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197",
- "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"
- ],
- "version": "==2022.2.1"
- },
"regex": {
"hashes": [
- "sha256:003a2e1449d425afc817b5f0b3d4c4aa9072dd5f3dfbf6c7631b8dc7b13233de",
- "sha256:0385d66e73cdd4462f3cc42c76a6576ddcc12472c30e02a2ae82061bff132c32",
- "sha256:0394265391a86e2bbaa7606e59ac71bd9f1edf8665a59e42771a9c9adbf6fd4f",
- "sha256:03ff695518482b946a6d3d4ce9cbbd99a21320e20d94913080aa3841f880abcd",
- "sha256:079c182f99c89524069b9cd96f5410d6af437e9dca576a7d59599a574972707e",
- "sha256:091efcfdd4178a7e19a23776dc2b1fafb4f57f4d94daf340f98335817056f874",
- "sha256:0b664a4d33ffc6be10996606dfc25fd3248c24cc589c0b139feb4c158053565e",
- "sha256:14216ea15efc13f28d0ef1c463d86d93ca7158a79cd4aec0f9273f6d4c6bb047",
- "sha256:14a7ab070fa3aec288076eed6ed828587b805ef83d37c9bfccc1a4a7cfbd8111",
- "sha256:14c71437ffb89479c89cc7022a5ea2075a842b728f37205e47c824cc17b30a42",
- "sha256:18e503b1e515a10282b3f14f1b3d856194ecece4250e850fad230842ed31227f",
- "sha256:19a4da6f513045f5ba00e491215bd00122e5bd131847586522463e5a6b2bd65f",
- "sha256:1a901ce5cd42658ab8f8eade51b71a6d26ad4b68c7cfc86b87efc577dfa95602",
- "sha256:26df88c9636a0c3f3bd9189dd435850a0c49d0b7d6e932500db3f99a6dd604d1",
- "sha256:2dda4b096a6f630d6531728a45bd12c67ec3badf44342046dc77d4897277d4f2",
- "sha256:322bd5572bed36a5b39952d88e072738926759422498a96df138d93384934ff8",
- "sha256:360ffbc9357794ae41336b681dff1c0463193199dfb91fcad3ec385ea4972f46",
- "sha256:37e5a26e76c46f54b3baf56a6fdd56df9db89758694516413757b7d127d4c57b",
- "sha256:3d64e1a7e6d98a4cdc8b29cb8d8ed38f73f49e55fbaa737bdb5933db99b9de22",
- "sha256:3f3b4594d564ed0b2f54463a9f328cf6a5b2a32610a90cdff778d6e3e561d08b",
- "sha256:4146cb7ae6029fc83b5c905ec6d806b7e5568dc14297c423e66b86294bad6c39",
- "sha256:4318f69b79f9f7d84a7420e97d4bfe872dc767c72f891d4fea5fa721c74685f7",
- "sha256:4cdbfa6d2befeaee0c899f19222e9b20fc5abbafe5e9c43a46ef819aeb7b75e5",
- "sha256:50e764ffbd08b06aa8c4e86b8b568b6722c75d301b33b259099f237c46b2134e",
- "sha256:518272f25da93e02af4f1e94985f5042cec21557ef3591027d0716f2adda5d0a",
- "sha256:592b9e2e1862168e71d9e612bfdc22c451261967dbd46681f14e76dfba7105fd",
- "sha256:59a786a55d00439d8fae4caaf71581f2aaef7297d04ee60345c3594efef5648a",
- "sha256:59bac44b5a07b08a261537f652c26993af9b1bbe2a29624473968dd42fc29d56",
- "sha256:5d0dd8b06896423211ce18fba0c75dacc49182a1d6514c004b535be7163dca0f",
- "sha256:67a4c625361db04ae40ef7c49d3cbe2c1f5ff10b5a4491327ab20f19f2fb5d40",
- "sha256:6adfe300848d61a470ec7547adc97b0ccf86de86a99e6830f1d8c8d19ecaf6b3",
- "sha256:6b32b45433df1fad7fed738fe15200b6516da888e0bd1fdd6aa5e50cc16b76bc",
- "sha256:6c57d50d4d5eb0c862569ca3c840eba2a73412f31d9ecc46ef0d6b2e621a592b",
- "sha256:6d43bd402b27e0e7eae85c612725ba1ce7798f20f6fab4e8bc3de4f263294f03",
- "sha256:6e521d9db006c5e4a0f8acfef738399f72b704913d4e083516774eb51645ad7c",
- "sha256:6fe1dd1021e0f8f3f454ce2811f1b0b148f2d25bb38c712fec00316551e93650",
- "sha256:73b985c9fc09a7896846e26d7b6f4d1fd5a20437055f4ef985d44729f9f928d0",
- "sha256:7681c49da1a2d4b905b4f53d86c9ba4506e79fba50c4a664d9516056e0f7dfcc",
- "sha256:77c2879d3ba51e5ca6c2b47f2dcf3d04a976a623a8fc8236010a16c9e0b0a3c7",
- "sha256:7b0c5cc3d1744a67c3b433dce91e5ef7c527d612354c1f1e8576d9e86bc5c5e2",
- "sha256:7fcf7f94ccad19186820ac67e2ec7e09e0ac2dac39689f11cf71eac580503296",
- "sha256:83cc32a1a2fa5bac00f4abc0e6ce142e3c05d3a6d57e23bd0f187c59b4e1e43b",
- "sha256:8418ee2cb857b83881b8f981e4c636bc50a0587b12d98cb9b947408a3c484fe7",
- "sha256:86df2049b18745f3cd4b0f4c4ef672bfac4b80ca488e6ecfd2bbfe68d2423a2c",
- "sha256:880dbeb6bdde7d926b4d8e41410b16ffcd4cb3b4c6d926280fea46e2615c7a01",
- "sha256:8aba0d01e3dfd335f2cb107079b07fdddb4cd7fb2d8c8a1986f9cb8ce9246c24",
- "sha256:8dcbcc9e72a791f622a32d17ff5011326a18996647509cac0609a7fc43adc229",
- "sha256:944567bb08f52268d8600ee5bdf1798b2b62ea002cc692a39cec113244cbdd0d",
- "sha256:995e70bb8c91d1b99ed2aaf8ec44863e06ad1dfbb45d7df95f76ef583ec323a9",
- "sha256:99945ddb4f379bb9831c05e9f80f02f079ba361a0fb1fba1fc3b267639b6bb2e",
- "sha256:9a165a05979e212b2c2d56a9f40b69c811c98a788964e669eb322de0a3e420b4",
- "sha256:9bc8edc5f8ef0ebb46f3fa0d02bd825bbe9cc63d59e428ffb6981ff9672f6de1",
- "sha256:a1aec4ae549fd7b3f52ceaf67e133010e2fba1538bf4d5fc5cd162a5e058d5df",
- "sha256:a1c4d17879dd4c4432c08a1ca1ab379f12ab54af569e945b6fc1c4cf6a74ca45",
- "sha256:a2b39ee3b280e15824298b97cec3f7cbbe6539d8282cc8a6047a455b9a72c598",
- "sha256:a2effeaf50a6838f3dd4d3c5d265f06eabc748f476e8441892645ae3a697e273",
- "sha256:a59d0377e58d96a6f11636e97992f5b51b7e1e89eb66332d1c01b35adbabfe8a",
- "sha256:a926339356fe29595f8e37af71db37cd87ff764e15da8ad5129bbaff35bcc5a6",
- "sha256:a9eb9558e1d0f78e07082d8a70d5c4d631c8dd75575fae92105df9e19c736730",
- "sha256:ab07934725e6f25c6f87465976cc69aef1141e86987af49d8c839c3ffd367c72",
- "sha256:ad75173349ad79f9d21e0d0896b27dcb37bfd233b09047bc0b4d226699cf5c87",
- "sha256:b7b701dbc124558fd2b1b08005eeca6c9160e209108fbcbd00091fcfac641ac7",
- "sha256:b7bee775ff05c9d519195bd9e8aaaccfe3971db60f89f89751ee0f234e8aeac5",
- "sha256:b86548b8234b2be3985dbc0b385e35f5038f0f3e6251464b827b83ebf4ed90e5",
- "sha256:b9d68eb704b24bc4d441b24e4a12653acd07d2c39940548761e0985a08bc1fff",
- "sha256:c0b7cb9598795b01f9a3dd3f770ab540889259def28a3bf9b2fa24d52edecba3",
- "sha256:cab548d6d972e1de584161487b2ac1aa82edd8430d1bde69587ba61698ad1cfb",
- "sha256:ce331b076b2b013e7d7f07157f957974ef0b0881a808e8a4a4b3b5105aee5d04",
- "sha256:cfa4c956ff0a977c4823cb3b930b0a4e82543b060733628fec7ab3eb9b1abe37",
- "sha256:d23ac6b4bf9e32fcde5fcdb2e1fd5e7370d6693fcac51ee1d340f0e886f50d1f",
- "sha256:d2885ec6eea629c648ecc9bde0837ec6b92208b7f36381689937fe5d64a517e8",
- "sha256:d2a1371dc73e921f3c2e087c05359050f3525a9a34b476ebc8130e71bec55e97",
- "sha256:d3102ab9bf16bf541ca228012d45d88d2a567c9682a805ae2c145a79d3141fdd",
- "sha256:d5b003d248e6f292475cd24b04e5f72c48412231961a675edcb653c70730e79e",
- "sha256:d5edd3eb877c9fc2e385173d4a4e1d792bf692d79e25c1ca391802d36ecfaa01",
- "sha256:d7430f041755801b712ec804aaf3b094b9b5facbaa93a6339812a8e00d7bd53a",
- "sha256:d837ccf3bd2474feabee96cd71144e991472e400ed26582edc8ca88ce259899c",
- "sha256:dab81cc4d58026861445230cfba27f9825e9223557926e7ec22156a1a140d55c",
- "sha256:db45016364eec9ddbb5af93c8740c5c92eb7f5fc8848d1ae04205a40a1a2efc6",
- "sha256:df8fe00b60e4717662c7f80c810ba66dcc77309183c76b7754c0dff6f1d42054",
- "sha256:e6e6e61e9a38b6cc60ca3e19caabc90261f070f23352e66307b3d21a24a34aaf",
- "sha256:ee7045623a5ace70f3765e452528b4c1f2ce669ed31959c63f54de64fe2f6ff7",
- "sha256:f06cc1190f3db3192ab8949e28f2c627e1809487e2cfc435b6524c1ce6a2f391",
- "sha256:f07373b6e56a6f3a0df3d75b651a278ca7bd357a796078a26a958ea1ce0588fd",
- "sha256:f6e0321921d2fdc082ef90c1fd0870f129c2e691bfdc4937dcb5cd308aba95c4",
- "sha256:f6e167d1ccd41d27b7b6655bb7a2dcb1b1eb1e0d2d662043470bd3b4315d8b2b",
- "sha256:fcbd1edff1473d90dc5cf4b52d355cf1f47b74eb7c85ba6e45f45d0116b8edbd",
- "sha256:fe428822b7a8c486bcd90b334e9ab541ce6cc0d6106993d59f201853e5e14121"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==2022.9.13"
- },
- "requests": {
- "hashes": [
- "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983",
- "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"
- ],
- "markers": "python_version >= '3.7' and python_version < '4'",
- "version": "==2.28.1"
+ "sha256:086afe222d58b88b62847bdbd92079b4699350b4acab892f88a935db5707c790",
+ "sha256:0b8eb1e3bca6b48dc721818a60ae83b8264d4089a4a41d62be6d05316ec38e15",
+ "sha256:11d00c31aeab9a6e0503bc77e73ed9f4527b3984279d997eb145d7c7be6268fd",
+ "sha256:11d1f2b7a0696dc0310de0efb51b1f4d813ad4401fe368e83c0c62f344429f98",
+ "sha256:1b1fc2632c01f42e06173d8dd9bb2e74ab9b0afa1d698058c867288d2c7a31f3",
+ "sha256:20abe0bdf03630fe92ccafc45a599bca8b3501f48d1de4f7d121153350a2f77d",
+ "sha256:22720024b90a6ba673a725dcc62e10fb1111b889305d7c6b887ac7466b74bedb",
+ "sha256:2472428efc4127374f494e570e36b30bb5e6b37d9a754f7667f7073e43b0abdd",
+ "sha256:25f0532fd0c53e96bad84664171969de9673b4131f2297f1db850d3918d58858",
+ "sha256:2848bf76673c83314068241c8d5b7fa9ad9bed866c979875a0e84039349e8fa7",
+ "sha256:37ae17d3be44c0b3f782c28ae9edd8b47c1f1776d4cabe87edc0b98e1f12b021",
+ "sha256:3cd9f5dd7b821f141d3a6ca0d5d9359b9221e4f051ca3139320adea9f1679691",
+ "sha256:4479f9e2abc03362df4045b1332d4a2b7885b245a30d4f4b051c4083b97d95d8",
+ "sha256:4c49552dc938e3588f63f8a78c86f3c9c75301e813bca0bef13bdb4b87ccf364",
+ "sha256:539dd010dc35af935b32f248099e38447bbffc10b59c2b542bceead2bed5c325",
+ "sha256:54c3fa855a3f7438149de3211738dd9b5f0c733f48b54ae05aa7fce83d48d858",
+ "sha256:55ae114da21b7a790b90255ea52d2aa3a0d121a646deb2d3c6a3194e722fc762",
+ "sha256:5ccfafd98473e007cebf7da10c1411035b7844f0f204015efd050601906dbb53",
+ "sha256:5fc33b27b1d800fc5b78d7f7d0f287e35079ecabe68e83d46930cf45690e1c8c",
+ "sha256:6560776ec19c83f3645bbc5db64a7a5816c9d8fb7ed7201c5bcd269323d88072",
+ "sha256:6572ff287176c0fb96568adb292674b421fa762153ed074d94b1d939ed92c253",
+ "sha256:6b190a339090e6af25f4a5fd9e77591f6d911cc7b96ecbb2114890b061be0ac1",
+ "sha256:7304863f3a652dab5e68e6fb1725d05ebab36ec0390676d1736e0571ebb713ef",
+ "sha256:75f288c60232a5339e0ff2fa05779a5e9c74e9fc085c81e931d4a264501e745b",
+ "sha256:7868b8f218bf69a2a15402fde08b08712213a1f4b85a156d90473a6fb6b12b09",
+ "sha256:787954f541ab95d8195d97b0b8cf1dc304424adb1e07365967e656b92b38a699",
+ "sha256:78ac8dd8e18800bb1f97aad0d73f68916592dddf233b99d2b5cabc562088503a",
+ "sha256:79e29fd62fa2f597a6754b247356bda14b866131a22444d67f907d6d341e10f3",
+ "sha256:845a5e2d84389c4ddada1a9b95c055320070f18bb76512608374aca00d22eca8",
+ "sha256:86b036f401895e854de9fefe061518e78d506d8a919cc250dc3416bca03f6f9a",
+ "sha256:87d9951f5a538dd1d016bdc0dcae59241d15fa94860964833a54d18197fcd134",
+ "sha256:8a9c63cde0eaa345795c0fdeb19dc62d22e378c50b0bc67bf4667cd5b482d98b",
+ "sha256:93f3f1aa608380fe294aa4cb82e2afda07a7598e828d0341e124b8fd9327c715",
+ "sha256:9bf4a5626f2a0ea006bf81e8963f498a57a47d58907eaa58f4b3e13be68759d8",
+ "sha256:9d764514d19b4edcc75fd8cb1423448ef393e8b6cbd94f38cab983ab1b75855d",
+ "sha256:a610e0adfcb0fc84ea25f6ea685e39e74cbcd9245a72a9a7aab85ff755a5ed27",
+ "sha256:a81c9ec59ca2303acd1ccd7b9ac409f1e478e40e96f8f79b943be476c5fdb8bb",
+ "sha256:b7006105b10b59971d3b248ad75acc3651c7e4cf54d81694df5a5130a3c3f7ea",
+ "sha256:c07ce8e9eee878a48ebeb32ee661b49504b85e164b05bebf25420705709fdd31",
+ "sha256:c125a02d22c555e68f7433bac8449992fa1cead525399f14e47c2d98f2f0e467",
+ "sha256:c37df2a060cb476d94c047b18572ee2b37c31f831df126c0da3cd9227b39253d",
+ "sha256:c869260aa62cee21c5eb171a466c0572b5e809213612ef8d495268cd2e34f20d",
+ "sha256:c88e8c226473b5549fe9616980ea7ca09289246cfbdf469241edf4741a620004",
+ "sha256:cd1671e9d5ac05ce6aa86874dd8dfa048824d1dbe73060851b310c6c1a201a96",
+ "sha256:cde09c4fdd070772aa2596d97e942eb775a478b32459e042e1be71b739d08b77",
+ "sha256:cf86b4328c204c3f315074a61bc1c06f8a75a8e102359f18ce99fbcbbf1951f0",
+ "sha256:d5bbe0e1511b844794a3be43d6c145001626ba9a6c1db8f84bdc724e91131d9d",
+ "sha256:d895b4c863059a4934d3e874b90998df774644a41b349ebb330f85f11b4ef2c0",
+ "sha256:db034255e72d2995cf581b14bb3fc9c00bdbe6822b49fcd4eef79e1d5f232618",
+ "sha256:dbb3f87e15d3dd76996d604af8678316ad2d7d20faa394e92d9394dfd621fd0c",
+ "sha256:dc80df325b43ffea5cdea2e3eaa97a44f3dd298262b1c7fe9dbb2a9522b956a7",
+ "sha256:dd7200b4c27b68cf9c9646da01647141c6db09f48cc5b51bc588deaf8e98a797",
+ "sha256:df45fac182ebc3c494460c644e853515cc24f5ad9da05f8ffb91da891bfee879",
+ "sha256:e152461e9a0aedec7d37fc66ec0fa635eca984777d3d3c3e36f53bf3d3ceb16e",
+ "sha256:e2396e0678167f2d0c197da942b0b3fb48fee2f0b5915a0feb84d11b6686afe6",
+ "sha256:e76b6fc0d8e9efa39100369a9b3379ce35e20f6c75365653cf58d282ad290f6f",
+ "sha256:ea3c0cb56eadbf4ab2277e7a095676370b3e46dbfc74d5c383bd87b0d6317910",
+ "sha256:ef3f528fe1cc3d139508fe1b22523745aa77b9d6cb5b0bf277f48788ee0b993f",
+ "sha256:fdf7ad455f1916b8ea5cdbc482d379f6daf93f3867b4232d14699867a5a13af7",
+ "sha256:fffe57312a358be6ec6baeb43d253c36e5790e436b7bf5b7a38df360363e88e9"
+ ],
+ "markers": "python_full_version >= '3.8.0'",
+ "version": "==2023.3.23"
},
"sqlparse": {
"hashes": [
- "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34",
- "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"
+ "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3",
+ "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"
],
- "markers": "python_version >= '3.5'",
- "version": "==0.4.3"
+ "markers": "python_full_version >= '3.5.0'",
+ "version": "==0.4.4"
},
"toml": {
"hashes": [
@@ -1936,6 +2045,14 @@
"markers": "python_version >= '3.6'",
"version": "==1.5.4"
},
+ "typing-extensions": {
+ "hashes": [
+ "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
+ "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==4.5.0"
+ },
"unittest-xml-reporting": {
"hashes": [
"sha256:edd8d3170b40c3a81b8cf910f46c6a304ae2847ec01036d02e9c0f9b85762d28",
@@ -1943,14 +2060,6 @@
],
"index": "pypi",
"version": "==3.2.0"
- },
- "urllib3": {
- "hashes": [
- "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e",
- "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==1.26.12"
}
}
}
diff --git a/backend/clubs/admin.py b/backend/clubs/admin.py
index 9dcd1c7c5..24557da10 100644
--- a/backend/clubs/admin.py
+++ b/backend/clubs/admin.py
@@ -14,6 +14,8 @@
AdminNote,
Advisor,
ApplicationCommittee,
+ ApplicationCycle,
+ ApplicationExtension,
ApplicationMultipleChoice,
ApplicationQuestion,
ApplicationQuestionResponse,
@@ -406,12 +408,18 @@ class ZoomMeetingVisitAdmin(admin.ModelAdmin):
list_filter = (("leave_time", admin.EmptyFieldListFilter),)
+class ApplicationSubmissionAdmin(admin.ModelAdmin):
+ search_fields = ("user__username",)
+ list_display = ("user", "id", "created_at", "status")
+
+
admin.site.register(Asset)
admin.site.register(ApplicationCommittee)
+admin.site.register(ApplicationExtension)
admin.site.register(ApplicationMultipleChoice)
admin.site.register(ApplicationQuestion)
admin.site.register(ApplicationQuestionResponse)
-admin.site.register(ApplicationSubmission)
+admin.site.register(ApplicationSubmission, ApplicationSubmissionAdmin)
admin.site.register(Advisor, AdvisorAdmin)
admin.site.register(Club, ClubAdmin)
admin.site.register(ClubFair, ClubFairAdmin)
@@ -445,5 +453,6 @@ class ZoomMeetingVisitAdmin(admin.ModelAdmin):
admin.site.register(Year, YearAdmin)
admin.site.register(ZoomMeetingVisit, ZoomMeetingVisitAdmin)
admin.site.register(AdminNote)
+admin.site.register(ApplicationCycle)
admin.site.register(Ticket)
admin.site.register(Cart)
diff --git a/backend/clubs/management/commands/populate.py b/backend/clubs/management/commands/populate.py
index a499bacef..57c8f3236 100644
--- a/backend/clubs/management/commands/populate.py
+++ b/backend/clubs/management/commands/populate.py
@@ -226,7 +226,7 @@
members and to answer questions.
- If you would like to particpate in the Fall 2020 SAC fair, check
+ If you would like to particpate in the SAC fair, check
the box below. If you check the box below, your club information
will be shared with the Student Activites Council and more details
will be sent to you at a later date.
@@ -396,6 +396,12 @@ def get_image(url):
tag_undergrad, _ = Tag.objects.get_or_create(name="Undergraduate")
tag_generic, _ = Tag.objects.get_or_create(name="Generic")
+ wharton_badge, _ = Badge.objects.get_or_create(
+ label="Wharton Council",
+ purpose="Dummy badge to mock Wharton-affiliated clubs",
+ visible=True,
+ )
+
for i in range(1, 50):
club, created = Club.objects.get_or_create(
code="z-club-{}".format(i),
@@ -408,6 +414,10 @@ def get_image(url):
},
)
+ if 10 <= i <= 15:
+ # Make some clubs Wharton-affiliated
+ club.badges.add(wharton_badge)
+
if created:
club.available_virtually = i % 2 == 0
club.appointment_needed = i % 3 == 0
diff --git a/backend/clubs/management/commands/update_club_counts.py b/backend/clubs/management/commands/update_club_counts.py
new file mode 100644
index 000000000..01ff17676
--- /dev/null
+++ b/backend/clubs/management/commands/update_club_counts.py
@@ -0,0 +1,36 @@
+from django.core.management.base import BaseCommand
+from django.db.models import Count, Q
+
+from clubs.models import Club
+
+
+class Command(BaseCommand):
+ help = "Update stored favorite and membership counts."
+
+ def handle(self, *args, **kwargs):
+ try:
+ queryset = Club.objects.all().annotate(
+ temp_favorite_count=Count("favorite", distinct=True),
+ temp_membership_count=Count(
+ "membership", distinct=True, filter=Q(active=True)
+ ),
+ )
+
+ for club in queryset:
+ club.favorite_count = club.temp_favorite_count
+ club.membership_count = club.temp_membership_count
+ Club.objects.bulk_update(queryset, ["favorite_count", "membership_count"])
+
+ self.stdout.write(
+ self.style.SUCCESS(
+ "Successfully updated all club favorite and membership counts!"
+ )
+ )
+ except Exception as e:
+ self.stdout.write(
+ self.style.ERROR(
+ "An error was encountered while updating"
+ + "club favorite and membership counts!"
+ )
+ )
+ self.stdout.write(e)
diff --git a/backend/clubs/management/commands/wharton_council_application.py b/backend/clubs/management/commands/wharton_council_application.py
deleted file mode 100644
index 3fe8c75af..000000000
--- a/backend/clubs/management/commands/wharton_council_application.py
+++ /dev/null
@@ -1,135 +0,0 @@
-from datetime import datetime
-
-from django.core.management.base import BaseCommand
-
-from clubs.models import (
- ApplicationMultipleChoice,
- ApplicationQuestion,
- Badge,
- Club,
- ClubApplication,
-)
-
-
-class Command(BaseCommand):
- help = "Helper to automatically create the Wharton council club applications."
- web_execute = True
-
- def add_arguments(self, parser):
- parser.add_argument(
- "application_start_time",
- type=str,
- help="Date and time at which the centralized application opens.",
- )
- parser.add_argument(
- "application_end_time",
- type=str,
- help="Date and time at which the centralized application closes.",
- )
- parser.add_argument(
- "result_release_time",
- type=str,
- help="Date and time at which the centralized application results "
- "are released.",
- )
- parser.add_argument(
- "--dry-run",
- dest="dry_run",
- action="store_true",
- help="Do not actually create applications.",
- )
- parser.add_argument(
- "--clubs",
- dest="clubs",
- type=str,
- help="The comma separated list of club codes for which to create the "
- "centralized applications.",
- )
- parser.set_defaults(
- application_start_time="2021-09-04 00:00:00",
- application_end_time="2021-09-04 00:00:00",
- result_release_time="2021-09-04 00:00:00",
- dry_run=False,
- clubs="",
- )
-
- def handle(self, *args, **kwargs):
- dry_run = kwargs["dry_run"]
- club_names = list(map(lambda x: x.strip(), kwargs["clubs"].split(",")))
- clubs = []
-
- if club_names == [] or all(not name for name in club_names):
- wc_badge = Badge.objects.filter(
- label="Wharton Council", purpose="org",
- ).first()
- for club in Club.objects.all():
- if wc_badge in club.badges.all():
- clubs.append(club)
- else:
- for code in club_names:
- target_club = Club.objects.filter(code=code).first()
- if target_club is not None:
- clubs.append(target_club)
-
- application_start_time = datetime.strptime(
- kwargs["application_start_time"], "%Y-%m-%d %H:%M:%S"
- )
- application_end_time = datetime.strptime(
- kwargs["application_end_time"], "%Y-%m-%d %H:%M:%S"
- )
- result_release_time = datetime.strptime(
- kwargs["result_release_time"], "%Y-%m-%d %H:%M:%S"
- )
-
- prompt_one = (
- "Tell us about a time you took " "initiative or demonstrated leadership"
- )
- prompt_two = "Tell us about a time you faced a challenge and how you solved it"
- prompt_three = "Tell us about a time you collaborated well in a team"
-
- if len(clubs) == 0:
- self.stdout.write("No valid club codes provided, returning...")
-
- for club in clubs:
- name = f"{club.name} Application"
- if dry_run:
- self.stdout.write(f"Would have created application for {club.name}")
- else:
- self.stdout.write(f"Creating application for {club.name}")
- application = ClubApplication.objects.create(
- name=name,
- club=club,
- application_start_time=application_start_time,
- application_end_time=application_end_time,
- result_release_time=result_release_time,
- is_wharton_council=True,
- )
- external_url = (
- f"https://pennclubs.com/club/{club.code}/"
- f"application/{application.pk}"
- )
- application.external_url = external_url
- application.save()
- prompt = (
- "Choose one of the following " "prompts for your personal statement"
- )
- prompt_question = ApplicationQuestion.objects.create(
- question_type=ApplicationQuestion.MULTIPLE_CHOICE,
- application=application,
- prompt=prompt,
- )
- ApplicationMultipleChoice.objects.create(
- value=prompt_one, question=prompt_question
- )
- ApplicationMultipleChoice.objects.create(
- value=prompt_two, question=prompt_question
- )
- ApplicationMultipleChoice.objects.create(
- value=prompt_three, question=prompt_question
- )
- ApplicationQuestion.objects.create(
- question_type=ApplicationQuestion.FREE_RESPONSE,
- prompt="Answer the prompt you selected",
- word_limit=150,
- application=application,
- )
diff --git a/backend/clubs/migrations/0090_adminnote.py b/backend/clubs/migrations/0088_adminnote.py
similarity index 95%
rename from backend/clubs/migrations/0090_adminnote.py
rename to backend/clubs/migrations/0088_adminnote.py
index d75043e60..e6ad481cc 100644
--- a/backend/clubs/migrations/0090_adminnote.py
+++ b/backend/clubs/migrations/0088_adminnote.py
@@ -9,7 +9,7 @@ class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ("clubs", "0089_alter_applicationsubmission_status"),
+ ("clubs", "0087_questionanswer_users_liked"),
]
operations = [
diff --git a/backend/clubs/migrations/0088_alter_applicationsubmission_status.py b/backend/clubs/migrations/0088_alter_applicationsubmission_status.py
deleted file mode 100644
index 5710f001a..000000000
--- a/backend/clubs/migrations/0088_alter_applicationsubmission_status.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Generated by Django 3.2.6 on 2021-10-06 21:33
-
-from django.db import migrations, models
-
-from clubs.models import ApplicationSubmission
-
-
-def reassign_status(apps, schema_editor):
- submissions = ApplicationSubmission.objects.all()
- for submission in submissions:
- if submission.status == 1:
- pass
- elif submission.status == 2:
- submission.status = ApplicationSubmission.PENDING
- elif submission.status == 3:
- submission.status = ApplicationSubmission.PENDING
- elif submission.status == 4:
- pass
- elif submission.status == 5:
- submission.status = ApplicationSubmission.REJECTED_AFTER_WRITTEN
- submission.save()
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("clubs", "0087_questionanswer_users_liked"),
- ]
-
- operations = [
- migrations.AlterField(
- model_name="applicationsubmission",
- name="status",
- field=models.IntegerField(
- choices=[
- (1, "Pending"),
- (2, "Rejected after interview(s)"),
- (3, "Rejected after written application"),
- (4, "Accepted"),
- ],
- default=1,
- ),
- ),
- migrations.RunPython(reassign_status),
- ]
diff --git a/backend/clubs/migrations/0089_alter_applicationsubmission_status.py b/backend/clubs/migrations/0089_alter_applicationsubmission_status.py
deleted file mode 100644
index 9db95bf25..000000000
--- a/backend/clubs/migrations/0089_alter_applicationsubmission_status.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Generated by Django 3.2.6 on 2021-10-06 22:40
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("clubs", "0088_alter_applicationsubmission_status"),
- ]
-
- operations = [
- migrations.AlterField(
- model_name="applicationsubmission",
- name="status",
- field=models.IntegerField(
- choices=[
- (1, "Pending"),
- (2, "Rejected after written application"),
- (3, "Rejected after interview(s)"),
- (4, "Accepted"),
- ],
- default=1,
- ),
- ),
- ]
diff --git a/backend/clubs/migrations/0089_auto_20230103_1239.py b/backend/clubs/migrations/0089_auto_20230103_1239.py
new file mode 100644
index 000000000..f027204f2
--- /dev/null
+++ b/backend/clubs/migrations/0089_auto_20230103_1239.py
@@ -0,0 +1,60 @@
+# Generated by Django 3.2.16 on 2023-01-03 17:39
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("clubs", "0088_adminnote"),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name="historicalclub",
+ options={
+ "get_latest_by": ("history_date", "history_id"),
+ "ordering": ("-history_date", "-history_id"),
+ "verbose_name": "historical club",
+ "verbose_name_plural": "historical clubs",
+ },
+ ),
+ migrations.AddField(
+ model_name="applicationsubmission",
+ name="notified",
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name="applicationsubmission",
+ name="reason",
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name="clubapplication",
+ name="acceptance_email",
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name="clubapplication",
+ name="rejection_email",
+ field=models.TextField(blank=True),
+ ),
+ migrations.AlterField(
+ model_name="applicationsubmission",
+ name="status",
+ field=models.IntegerField(
+ choices=[
+ (1, "Pending"),
+ (2, "Rejected after written application"),
+ (3, "Rejected after interview(s)"),
+ (4, "Accepted"),
+ ],
+ default=1,
+ ),
+ ),
+ migrations.AlterField(
+ model_name="historicalclub",
+ name="history_date",
+ field=models.DateTimeField(db_index=True),
+ ),
+ ]
diff --git a/backend/clubs/migrations/0090_auto_20230106_1443.py b/backend/clubs/migrations/0090_auto_20230106_1443.py
new file mode 100644
index 000000000..d7ce13226
--- /dev/null
+++ b/backend/clubs/migrations/0090_auto_20230106_1443.py
@@ -0,0 +1,40 @@
+# Generated by Django 3.2.16 on 2023-01-06 19:43
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("clubs", "0089_auto_20230103_1239"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="ApplicationCycle",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=255)),
+ ("start_date", models.DateTimeField(null=True)),
+ ("end_date", models.DateTimeField(null=True)),
+ ],
+ ),
+ migrations.AddField(
+ model_name="clubapplication",
+ name="application_cycle",
+ field=models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to="clubs.applicationcycle",
+ ),
+ ),
+ ]
diff --git a/backend/clubs/migrations/0091_applicationextension.py b/backend/clubs/migrations/0091_applicationextension.py
new file mode 100644
index 000000000..43cd96efd
--- /dev/null
+++ b/backend/clubs/migrations/0091_applicationextension.py
@@ -0,0 +1,47 @@
+# Generated by Django 3.2.18 on 2023-11-25 03:58
+
+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),
+ ("clubs", "0090_auto_20230106_1443"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="ApplicationExtension",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("end_time", models.DateTimeField()),
+ (
+ "application",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="extensions",
+ to="clubs.clubapplication",
+ ),
+ ),
+ (
+ "user",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ options={"unique_together": {("user", "application")}},
+ ),
+ ]
diff --git a/backend/clubs/migrations/0091_clubapplication_application_end_time_exception.py b/backend/clubs/migrations/0091_clubapplication_application_end_time_exception.py
new file mode 100644
index 000000000..92377bdb5
--- /dev/null
+++ b/backend/clubs/migrations/0091_clubapplication_application_end_time_exception.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.18 on 2023-11-17 22:01
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("clubs", "0090_auto_20230106_1443"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="clubapplication",
+ name="application_end_time_exception",
+ field=models.BooleanField(blank=True, default=False),
+ ),
+ ]
diff --git a/backend/clubs/migrations/0092_merge_20240106_1117.py b/backend/clubs/migrations/0092_merge_20240106_1117.py
new file mode 100644
index 000000000..9656c2c26
--- /dev/null
+++ b/backend/clubs/migrations/0092_merge_20240106_1117.py
@@ -0,0 +1,13 @@
+# Generated by Django 3.2.18 on 2024-01-06 16:17
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("clubs", "0091_applicationextension"),
+ ("clubs", "0091_clubapplication_application_end_time_exception"),
+ ]
+
+ operations = []
diff --git a/backend/clubs/migrations/0093_auto_20240106_1153.py b/backend/clubs/migrations/0093_auto_20240106_1153.py
new file mode 100644
index 000000000..1e2ab1a0f
--- /dev/null
+++ b/backend/clubs/migrations/0093_auto_20240106_1153.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.18 on 2024-01-06 16:53
+
+from django.conf import settings
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ("clubs", "0092_merge_20240106_1117"),
+ ]
+
+ operations = [
+ migrations.AlterUniqueTogether(
+ name="applicationquestionresponse",
+ unique_together={("question", "submission")},
+ ),
+ migrations.AlterUniqueTogether(
+ name="applicationsubmission",
+ unique_together={("user", "application", "committee")},
+ ),
+ ]
diff --git a/backend/clubs/migrations/0094_applicationcycle_release_date.py b/backend/clubs/migrations/0094_applicationcycle_release_date.py
new file mode 100644
index 000000000..62ad134f9
--- /dev/null
+++ b/backend/clubs/migrations/0094_applicationcycle_release_date.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.18 on 2024-01-11 14:39
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("clubs", "0093_auto_20240106_1153"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="applicationcycle",
+ name="release_date",
+ field=models.DateTimeField(null=True),
+ ),
+ ]
diff --git a/backend/clubs/migrations/0095_rm_field_add_count.py b/backend/clubs/migrations/0095_rm_field_add_count.py
new file mode 100644
index 000000000..f3ada60c1
--- /dev/null
+++ b/backend/clubs/migrations/0095_rm_field_add_count.py
@@ -0,0 +1,34 @@
+# Generated by Django 3.2.18 on 2024-02-03 22:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("clubs", "0094_applicationcycle_release_date"),
+ ]
+
+ operations = [
+ migrations.RemoveField(model_name="applicationsubmission", name="archived",),
+ migrations.AddField(
+ model_name="club",
+ name="favorite_count",
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name="club",
+ name="membership_count",
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name="historicalclub",
+ name="favorite_count",
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name="historicalclub",
+ name="membership_count",
+ field=models.IntegerField(default=0),
+ ),
+ ]
diff --git a/backend/clubs/migrations/0091_cart_ticket.py b/backend/clubs/migrations/0096_cart_ticket.py
similarity index 98%
rename from backend/clubs/migrations/0091_cart_ticket.py
rename to backend/clubs/migrations/0096_cart_ticket.py
index 74ae8cdd9..54b1afa63 100644
--- a/backend/clubs/migrations/0091_cart_ticket.py
+++ b/backend/clubs/migrations/0096_cart_ticket.py
@@ -11,7 +11,7 @@ class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ("clubs", "0090_adminnote"),
+ ("clubs", "0095_rm_field_add_count"),
]
operations = [
diff --git a/backend/clubs/mixins.py b/backend/clubs/mixins.py
index 22afe3964..16fc46137 100644
--- a/backend/clubs/mixins.py
+++ b/backend/clubs/mixins.py
@@ -148,7 +148,7 @@ def get_xlsx_column_name(self, key):
if hasattr(serializer_class, "get_xlsx_column_name"):
val = serializer_class.get_xlsx_column_name(key)
if val is None:
- val = key.replace("_", " ").title()
+ val = key.replace("_", " ")
self._column_cache[key] = val
return val
diff --git a/backend/clubs/models.py b/backend/clubs/models.py
index 4e7026497..cf622c0ae 100644
--- a/backend/clubs/models.py
+++ b/backend/clubs/models.py
@@ -22,7 +22,9 @@
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.crypto import get_random_string
+from django.utils.functional import cached_property
from ics import Calendar
+from jinja2 import Environment, meta
from model_clone.models import CloneModel
from phonenumber_field.modelfields import PhoneNumberField
from simple_history.models import HistoricalRecords
@@ -323,6 +325,13 @@ class Club(models.Model):
appointment_needed = models.BooleanField(default=False)
signature_events = models.TextField(blank=True) # html
+ # cache club aggregation counts
+ favorite_count = models.IntegerField(default=0)
+ membership_count = models.IntegerField(default=0)
+
+ # cache club rankings
+ rank = models.IntegerField(default=0)
+
# cache club rankings
rank = models.IntegerField(default=0)
@@ -338,6 +347,11 @@ def __str__(self):
def create_thumbnail(self, request=None):
return create_thumbnail_helper(self, request, 200)
+ @cached_property
+ def is_wharton(self):
+ wc_badge = Badge.objects.filter(label="Wharton Council").first()
+ return wc_badge in self.badges.all()
+
def add_ics_events(self):
"""
Fetch the ICS events from the club's calendar URL
@@ -1526,22 +1540,43 @@ def __str__(self):
return self.user.username
+class ApplicationCycle(models.Model):
+ """
+ Represents an application cycle attached to club applications
+ """
+
+ name = models.CharField(max_length=255)
+ start_date = models.DateTimeField(null=True)
+ end_date = models.DateTimeField(null=True)
+ release_date = models.DateTimeField(null=True)
+
+ def __str__(self):
+ return self.name
+
+
class ClubApplication(CloneModel):
"""
Represents custom club application.
"""
DEFAULT_COMMITTEE = "General Member"
+ VALID_TEMPLATE_TOKENS = {"name", "reason", "committee"}
club = models.ForeignKey(Club, on_delete=models.CASCADE)
description = models.TextField(blank=True)
application_start_time = models.DateTimeField()
application_end_time = models.DateTimeField()
+ application_end_time_exception = models.BooleanField(default=False, blank=True)
name = models.TextField(blank=True)
result_release_time = models.DateTimeField()
+ application_cycle = models.ForeignKey(
+ ApplicationCycle, on_delete=models.SET_NULL, null=True
+ )
external_url = models.URLField(blank=True)
is_active = models.BooleanField(default=False, blank=True)
is_wharton_council = models.BooleanField(default=False, blank=True)
+ acceptance_email = models.TextField(blank=True)
+ rejection_email = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -1559,12 +1594,53 @@ def __str__(self):
self.application_end_time,
)
- @property
+ @cached_property
def season(self):
- semester = "Fall" if 8 <= self.application_start_time.month <= 11 else "Spring"
+ semester = "Fall" if 8 <= self.application_start_time.month <= 12 else "Spring"
year = str(self.application_start_time.year)
return f"{semester} {year}"
+ @classmethod
+ def validate_template(cls, template):
+ environment = Environment()
+ j2_template = environment.parse(template)
+ tokens = meta.find_undeclared_variables(j2_template)
+ return all(t in cls.VALID_TEMPLATE_TOKENS for t in tokens)
+
+
+class ApplicationExtension(models.Model):
+ """
+ Represents an individual club application extension.
+ """
+
+ user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
+ application = models.ForeignKey(
+ ClubApplication, related_name="extensions", on_delete=models.CASCADE
+ )
+ end_time = models.DateTimeField()
+
+ def send_extension_mail(self):
+ context = {
+ "name": self.user.first_name,
+ "application_name": self.application.name,
+ "end_time": self.end_time,
+ "club": self.application.club.name,
+ "url": (
+ f"https://pennclubs.com/club/{self.application.club.code}"
+ f"/application/{self.application.pk}/"
+ ),
+ }
+
+ send_mail_helper(
+ name="application_extension",
+ subject=f"Application Extension for {self.application.name}",
+ emails=[self.user.email],
+ context=context,
+ )
+
+ class Meta:
+ unique_together = (("user", "application"),)
+
class ApplicationCommittee(models.Model):
"""
@@ -1648,6 +1724,7 @@ class ApplicationSubmission(models.Model):
)
status = models.IntegerField(choices=STATUS_TYPES, default=PENDING)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, null=False)
+ reason = models.TextField(blank=True)
application = models.ForeignKey(
ClubApplication,
related_name="submissions",
@@ -1660,10 +1737,16 @@ class ApplicationSubmission(models.Model):
on_delete=models.SET_NULL,
null=True,
)
- archived = models.BooleanField(default=False)
+ notified = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
+ def __str__(self):
+ return f"{self.user.first_name}: {self.application.name}"
+
+ class Meta:
+ unique_together = (("user", "application", "committee"),)
+
class ApplicationQuestionResponse(models.Model):
"""
@@ -1692,6 +1775,9 @@ class ApplicationQuestionResponse(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
+ class Meta:
+ unique_together = (("question", "submission"),)
+
class QuestionResponse(models.Model):
"""
diff --git a/backend/clubs/serializers.py b/backend/clubs/serializers.py
index f44761caa..573a58e4f 100644
--- a/backend/clubs/serializers.py
+++ b/backend/clubs/serializers.py
@@ -1,13 +1,12 @@
-import datetime
import json
import re
from collections import OrderedDict
from urllib.parse import parse_qs, urlparse
import bleach
-import pytz
from django.conf import settings
from django.contrib.auth import get_user_model
+from django.core.cache import cache
from django.core.exceptions import ValidationError as DjangoValidationError
from django.core.validators import URLValidator
from django.db import models
@@ -22,6 +21,8 @@
AdminNote,
Advisor,
ApplicationCommittee,
+ ApplicationCycle,
+ ApplicationExtension,
ApplicationMultipleChoice,
ApplicationQuestion,
ApplicationQuestionResponse,
@@ -96,6 +97,28 @@ def save(self):
return super().save()
+class ApplicationCycleSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ApplicationCycle
+ fields = ["id", "name", "start_date", "end_date", "release_date"]
+
+ def validate(self, data):
+ """
+ Check that start_date <= end_date <= release_date
+ """
+ start_date = data.get("start_date")
+ end_date = data.get("end_date")
+ release_date = data.get("release_date")
+
+ if start_date and end_date and start_date >= end_date:
+ raise serializers.ValidationError("Start must be before end.")
+
+ if end_date and release_date and end_date >= release_date:
+ raise serializers.ValidationError("End must be before release.")
+
+ return data
+
+
class TagSerializer(serializers.ModelSerializer):
clubs = serializers.IntegerField(read_only=True)
@@ -1015,6 +1038,7 @@ class Meta:
"is_favorite",
"is_member",
"is_subscribe",
+ "is_wharton",
"membership_count",
"recruiting_cycle",
"name",
@@ -2196,6 +2220,7 @@ class Meta(ClubSerializer.Meta):
"terms",
"owners",
"officers",
+ "approved_on",
]
@@ -2444,6 +2469,86 @@ class Meta:
fields = ("text", "multiple_choice", "question_type", "question")
+class ApplicationExtensionSerializer(serializers.ModelSerializer):
+ first_name = serializers.CharField(source="user.first_name", read_only=True)
+ last_name = serializers.CharField(source="user.last_name", read_only=True)
+ username = serializers.CharField(source="user.username", read_only=False)
+ graduation_year = serializers.CharField(
+ source="user.profile.graduation_year", read_only=True
+ )
+
+ class Meta:
+ model = ApplicationExtension
+ fields = (
+ "id",
+ "username",
+ "first_name",
+ "last_name",
+ "graduation_year",
+ "end_time",
+ )
+
+ def create(self, validated_data):
+ username = validated_data.get("user").pop("username")
+ validated_data["user"] = get_user_model().objects.get(username=username)
+
+ application_pk = self.context["view"].kwargs.get("application_pk")
+ validated_data["application"] = ClubApplication.objects.filter(
+ pk=application_pk
+ ).first()
+
+ return super().create(validated_data)
+
+ def update(self, instance, validated_data):
+ if user_field := validated_data.pop("user", None):
+ username = user_field.pop("username")
+ user = get_user_model().objects.get(username=username)
+ instance.user = user
+ return super().update(instance, validated_data)
+
+ def validate(self, data):
+ username = None
+ if user_field := data.get("user") or not self.instance:
+ username = user_field.get("username")
+ user = get_user_model().objects.filter(username=username).first()
+ if not user:
+ raise serializers.ValidationError("Please provide a valid username!")
+
+ application_pk = self.context["view"].kwargs.get("application_pk")
+ application = ClubApplication.objects.filter(pk=application_pk).first()
+
+ if not application:
+ raise serializers.ValidationError("Invalid application id!")
+
+ extension_exists = ApplicationExtension.objects.filter(
+ user=user, application=application
+ ).exists()
+ modify_username = not self.instance or (
+ username and self.instance.user.username != username
+ )
+
+ if modify_username and extension_exists:
+ raise serializers.ValidationError(
+ "An extension for this user and application already exists!"
+ )
+
+ extension_end_time = data.get("end_time")
+ if (
+ extension_end_time
+ and extension_end_time <= application.application_end_time
+ ):
+ raise serializers.ValidationError(
+ "Extension end time must be greater than the application end time!"
+ )
+
+ return data
+
+ def save(self):
+ extension_obj = super().save()
+ extension_obj.send_extension_mail()
+ return extension_obj
+
+
class ApplicationSubmissionSerializer(serializers.ModelSerializer):
committee = ApplicationCommitteeSerializer(required=False, read_only=True)
responses = ApplicationQuestionResponseSerializer(
@@ -2472,23 +2577,6 @@ def get_application_link(self, obj):
# cannot link to the application if the application has been deleted
return "#"
- def validate(self, data):
- application_start_time = data["application_start_time"]
- application_end_time = data["application_end_time"]
- now = pytz.UTC.localize(datetime.datetime.now())
-
- if now < application_start_time:
- raise serializers.ValidationError(
- "You cannot submit before the application has opened."
- )
-
- if now > application_end_time:
- raise serializers.ValidationError(
- "You cannot submit after the application deadline."
- )
-
- return data
-
class Meta:
model = ApplicationSubmission
fields = (
@@ -2500,6 +2588,8 @@ class Meta:
"status",
"responses",
"club",
+ "notified",
+ "reason",
"name",
"application_link",
"first_name",
@@ -2508,6 +2598,7 @@ class Meta:
"code",
"graduation_year",
)
+ read_only_fields = fields
class ApplicationSubmissionUserSerializer(ApplicationSubmissionSerializer):
@@ -2542,6 +2633,10 @@ class ApplicationSubmissionCSVSerializer(serializers.ModelSerializer):
email = serializers.CharField(source="user.email")
graduation_year = serializers.CharField(source="user.profile.graduation_year")
committee = serializers.SerializerMethodField("get_committee")
+ status = serializers.SerializerMethodField("get_status")
+
+ def get_status(self, obj):
+ return dict(ApplicationSubmission.STATUS_TYPES).get(obj, "Unknown")
def get_name(self, obj):
"""
@@ -2617,11 +2712,15 @@ class Meta:
"email",
"graduation_year",
"committee",
+ "notified",
+ "reason",
+ "status",
)
class ClubApplicationSerializer(ClubRouteMixin, serializers.ModelSerializer):
name = serializers.SerializerMethodField("get_name")
+ cycle = serializers.SerializerMethodField("get_cycle")
committees = ApplicationCommitteeSerializer(
many=True, required=False, read_only=True
)
@@ -2629,9 +2728,16 @@ class ClubApplicationSerializer(ClubRouteMixin, serializers.ModelSerializer):
club = serializers.SlugRelatedField(slug_field="code", read_only=True)
updated_at = serializers.SerializerMethodField("get_updated_time", read_only=True)
club_image_url = serializers.SerializerMethodField("get_image_url", read_only=True)
- season = serializers.CharField(read_only=True)
+ external_url = serializers.SerializerMethodField("get_external_url")
active = serializers.SerializerMethodField("get_active", read_only=True)
+ def get_external_url(self, obj):
+ default_url = f"https://pennclubs.com/club/{obj.club.code}/application/{obj.pk}"
+ return obj.external_url if obj.external_url else default_url
+
+ def get_cycle(self, obj):
+ return obj.application_cycle.name if obj.application_cycle else obj.season
+
def get_active(self, obj):
now = timezone.now()
return obj.application_end_time >= now
@@ -2661,20 +2767,45 @@ def get_image_url(self, obj):
return image.url
def validate(self, data):
- application_start_time = data["application_start_time"]
- application_end_time = data["application_end_time"]
- result_release_time = data["result_release_time"]
+ acceptance_template = data.get("acceptance_email", "")
+ rejection_template = data.get("rejection_email", "")
+ request = self.context["request"].data
- if application_start_time > application_end_time:
+ if "committees" in request and data["application_start_time"] < timezone.now():
raise serializers.ValidationError(
- "Your application start time must be less than the end time!"
+ "You cannot edit committees once the application is open"
)
- if application_end_time > result_release_time:
+ if not ClubApplication.validate_template(
+ acceptance_template
+ ) or not ClubApplication.validate_template(rejection_template):
raise serializers.ValidationError(
- "Your application end time must be less than the result release time!"
+ "Your application email templates contain invalid variables!"
)
+ if all(
+ field in data
+ for field in [
+ "application_start_time",
+ "application_end_time",
+ "result_release_time",
+ ]
+ ):
+ application_start_time = data["application_start_time"]
+ application_end_time = data["application_end_time"]
+ result_release_time = data["result_release_time"]
+
+ if application_start_time > application_end_time:
+ raise serializers.ValidationError(
+ "Your application start time must be less than the end time!"
+ )
+
+ if application_end_time > result_release_time:
+ raise serializers.ValidationError(
+ """Your application end time must be less than
+ the result release time!"""
+ )
+
return data
def save(self):
@@ -2684,7 +2815,7 @@ def save(self):
request = self.context["request"].data
# only allow modifications to committees if the application is not yet open
- now = pytz.timezone("America/New_York").localize(datetime.datetime.now())
+ now = timezone.now()
if "committees" in request and application_obj.application_start_time > now:
committees = map(
lambda x: x["value"] if "value" in x else x["name"],
@@ -2694,16 +2825,17 @@ def save(self):
application=application_obj
)
# nasty hack for idempotency
+ prev_committee_names = prev_committees.values("name")
for prev_committee in prev_committees:
if prev_committee.name not in committees:
prev_committee.delete()
- prev_committee_names = prev_committees.values("name")
for name in committees:
if name not in prev_committee_names:
ApplicationCommittee.objects.create(
name=name, application=application_obj,
)
+ cache.delete(f"clubapplication:{application_obj.id}")
return application_obj
@@ -2711,11 +2843,14 @@ class Meta:
model = ClubApplication
fields = (
"id",
- "season",
"active",
"name",
+ "cycle",
+ "acceptance_email",
+ "rejection_email",
"application_start_time",
"application_end_time",
+ "application_end_time_exception",
"result_release_time",
"external_url",
"committees",
@@ -2734,6 +2869,18 @@ class Meta(ClubApplicationSerializer.Meta):
pass
+class ManagedClubApplicationSerializer(ClubApplicationSerializer):
+ name = serializers.CharField(required=False, allow_blank=True)
+
+ class Meta(ClubApplicationSerializer.Meta):
+ read_only_fields = (
+ "external_url",
+ "application_start_time",
+ "application_end_time",
+ "result_release_time",
+ )
+
+
class NoteSerializer(ManyToManySaveMixin, serializers.ModelSerializer):
creator = serializers.HiddenField(default=serializers.CurrentUserDefault())
creating_club = serializers.SlugRelatedField(
diff --git a/backend/clubs/urls.py b/backend/clubs/urls.py
index 1a740ccb2..7da672904 100644
--- a/backend/clubs/urls.py
+++ b/backend/clubs/urls.py
@@ -4,6 +4,7 @@
from clubs.views import (
AdminNoteViewSet,
AdvisorViewSet,
+ ApplicationExtensionViewSet,
ApplicationQuestionViewSet,
ApplicationSubmissionUserViewSet,
ApplicationSubmissionViewSet,
@@ -51,6 +52,7 @@
UserZoomAPIView,
WhartonApplicationAPIView,
WhartonApplicationStatusAPIView,
+ WhartonCyclesView,
YearViewSet,
email_preview,
)
@@ -79,6 +81,12 @@
router.register(
r"external/members/(?P.+)", ExternalMemberListViewSet, basename="external"
)
+router.register(
+ r"cycles", WhartonCyclesView, basename="wharton-applications-create",
+)
+router.register(
+ r"whartonapplications", WhartonApplicationAPIView, basename="wharton",
+)
router.register(r"submissions", ApplicationSubmissionUserViewSet, basename="submission")
clubs_router = routers.NestedSimpleRouter(router, r"clubs", lookup="club")
@@ -115,6 +123,10 @@
basename="club-application-submissions",
)
+applications_router.register(
+ r"extensions", ApplicationExtensionViewSet, basename="club-application-extensions"
+)
+
router.register(r"booths", ClubBoothsViewSet, basename="club-booth")
urlpatterns = [
@@ -153,11 +165,6 @@
MeetingZoomWebhookAPIView.as_view(),
name="webhooks-meeting",
),
- path(
- r"whartonapplications/",
- WhartonApplicationAPIView.as_view(),
- name="wharton-applications",
- ),
path(
r"whartonapplications/status/",
WhartonApplicationStatusAPIView.as_view(),
diff --git a/backend/clubs/views.py b/backend/clubs/views.py
index f8e3bcefd..931d3ad1c 100644
--- a/backend/clubs/views.py
+++ b/backend/clubs/views.py
@@ -11,6 +11,7 @@
from functools import wraps
from urllib.parse import urlparse
+import pandas as pd
import pytz
import qrcode
import requests
@@ -20,9 +21,11 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
+from django.core import mail
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import UploadedFile
+from django.core.mail import EmailMultiAlternatives
from django.core.management import call_command, get_commands, load_command_class
from django.core.serializers.json import DjangoJSONEncoder
from django.core.validators import validate_email
@@ -36,18 +39,21 @@
Q,
TextField,
)
-from django.db.models.expressions import RawSQL, Value
-from django.db.models.functions import SHA1, Lower, Trunc
-from django.db.models.functions.text import Concat
+from django.db.models.expressions import Value
+from django.db.models.functions import SHA1, Concat, Lower, Trunc
from django.db.models.query import prefetch_related_objects
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from django.template.loader import render_to_string
from django.utils import timezone
+from django.utils.decorators import method_decorator
from django.utils.text import slugify
+from django.views.decorators.cache import cache_page
+from django.views.decorators.vary import vary_on_cookie
from ics import Calendar as ICSCal
from ics import Event as ICSEvent
from ics import parse as ICSParse
+from jinja2 import Template
from options.models import Option
from rest_framework import filters, generics, parsers, serializers, status, viewsets
from rest_framework.decorators import action
@@ -65,6 +71,8 @@
from clubs.models import (
AdminNote,
Advisor,
+ ApplicationCycle,
+ ApplicationExtension,
ApplicationMultipleChoice,
ApplicationQuestion,
ApplicationQuestionResponse,
@@ -122,6 +130,8 @@
from clubs.serializers import (
AdminNoteSerializer,
AdvisorSerializer,
+ ApplicationCycleSerializer,
+ ApplicationExtensionSerializer,
ApplicationQuestionResponseSerializer,
ApplicationQuestionSerializer,
ApplicationSubmissionCSVSerializer,
@@ -146,6 +156,7 @@
FavoriteWriteSerializer,
FavouriteEventSerializer,
MajorSerializer,
+ ManagedClubApplicationSerializer,
MembershipInviteSerializer,
MembershipRequestSerializer,
MembershipSerializer,
@@ -1021,13 +1032,7 @@ class ClubViewSet(XLSXFormatterMixin, viewsets.ModelViewSet):
"""
queryset = (
- Club.objects.all()
- .annotate(
- favorite_count=Count("favorite", distinct=True),
- membership_count=Count("membership", distinct=True, filter=Q(active=True)),
- )
- .prefetch_related("tags")
- .order_by("-favorite_count", "name")
+ Club.objects.all().prefetch_related("tags").order_by("-favorite_count", "name")
)
permission_classes = [ClubPermission | IsSuperuser]
filter_backends = [filters.SearchFilter, ClubsSearchFilter, ClubsOrderingFilter]
@@ -1152,6 +1157,8 @@ def upload(self, request, *args, **kwargs):
"""
# ensure user is allowed to upload image
club = self.get_object()
+ key = f"clubs:{club.id}"
+ cache.delete(key)
# reset approval status after upload
resp = upload_endpoint_helper(request, club, "file", "image", save=False)
@@ -1938,20 +1945,54 @@ def check_approval_permission(self, request):
"or deregistering for the SAC fair."
)
- def partial_update(self, request, *args, **kwargs):
- self.check_approval_permission(request)
- return super().partial_update(request, *args, **kwargs)
+ def list(self, *args, **kwargs):
+ """
+ Return a list of all clubs. Responses cached for 1 hour
+
+ Responses are only cached for people with specific permissions
+ """
+ key = self.request.build_absolute_uri()
+ cached_object = cache.get(key)
+ if (
+ cached_object
+ and not self.request.user.groups.filter(name="Approvers").exists()
+ ):
+ return Response(cached_object)
+
+ resp = super().list(*args, **kwargs)
+ cache.set(key, resp.data, 60 * 60)
+ return resp
+
+ def retrieve(self, *args, **kwargs):
+ """
+ Retrieve data about a specific club. Responses cached for 1 hour
+ """
+ key = f"clubs:{self.get_object().id}"
+ cached = cache.get(key)
+ if cached:
+ return Response(cached)
+
+ resp = super().retrieve(*args, **kwargs)
+ cache.set(key, resp.data, 60 * 60)
+ return resp
def update(self, request, *args, **kwargs):
+ """
+ Invalidate caches
+ """
self.check_approval_permission(request)
+ key = f"clubs:{self.get_object().id}"
+ cache.delete(key)
return super().update(request, *args, **kwargs)
- def list(self, request, *args, **kwargs):
+ def partial_update(self, request, *args, **kwargs):
"""
- Return a list of all clubs.
- Note that some fields are removed in order to improve response time.
+ Invalidate caches
"""
- return super().list(request, *args, **kwargs)
+ self.check_approval_permission(request)
+ key = f"clubs:{self.get_object().id}"
+ cache.delete(key)
+ return super().partial_update(request, *args, **kwargs)
def perform_destroy(self, instance):
"""
@@ -2044,7 +2085,10 @@ def get_serializer_class(self):
self.request.accepted_renderer.format == "xlsx"
or self.action == "fields"
):
- if self.request.user.has_perm("clubs.generate_reports"):
+ if (
+ self.request.user.has_perm("clubs.generate_reports")
+ or self.request.user.is_superuser
+ ):
return ReportClubSerializer
else:
return ClubSerializer
@@ -3945,9 +3989,8 @@ def post(self, request):
meeting_id = (
request.data.get("payload", {}).get("object", {}).get("id", None)
)
- regex = (
- rf"https?:\/\/([A-z]*.)?zoom.us/[^\/]*\/{meeting_id}(\?pwd=[A-z,0-9]*)?"
- )
+ regex = rf"""https?:\/\/([A-z]*\.)?zoom\.us/[^\/]*\/
+ {meeting_id}(\?pwd=[A-z,0-9]*)?"""
event = Event.objects.filter(url__regex=regex).first()
participant_id = (
@@ -4888,12 +4931,24 @@ def question_response(self, *args, **kwargs):
user=self.request.user,
committee__isnull=False,
application=application,
- archived=False,
)
.values_list("committee__name", flat=True)
.distinct()
)
+ # prevent submissions outside of the open duration
+ now = timezone.now()
+ extension = application.extensions.filter(user=self.request.user).first()
+ end_time = (
+ max(extension.end_time, application.application_end_time)
+ if extension
+ else application.application_end_time
+ )
+ if now > end_time or now < application.application_start_time:
+ return Response(
+ {"success": False, "detail": "This application is not currently open!"}
+ )
+
# limit applicants to 2 committees
if (
committee
@@ -4909,9 +4964,13 @@ def question_response(self, *args, **kwargs):
submissions page""",
}
)
- submission = ApplicationSubmission.objects.create(
+ submission, _ = ApplicationSubmission.objects.get_or_create(
user=self.request.user, application=application, committee=committee,
)
+
+ key = f"applicationsubmissions:{application.id}"
+ cache.delete(key)
+
for question_pk in questions:
question = ApplicationQuestion.objects.filter(pk=question_pk).first()
question_type = question.question_type
@@ -4930,9 +4989,11 @@ def question_response(self, *args, **kwargs):
):
text = question_data.get("text", None)
if text is not None and text != "":
- obj = ApplicationQuestionResponse.objects.create(
- text=text, question=question, submission=submission,
- ).save()
+ obj, _ = ApplicationQuestionResponse.objects.update_or_create(
+ question=question,
+ submission=submission,
+ defaults={"text": text},
+ )
response = Response(ApplicationQuestionResponseSerializer(obj).data)
elif question_type == ApplicationQuestion.MULTIPLE_CHOICE:
multiple_choice_value = question_data.get("multipleChoice", None)
@@ -4940,13 +5001,12 @@ def question_response(self, *args, **kwargs):
multiple_choice_obj = ApplicationMultipleChoice.objects.filter(
question=question, value=multiple_choice_value
).first()
- obj = ApplicationQuestionResponse.objects.create(
- multiple_choice=multiple_choice_obj,
+ obj, _ = ApplicationQuestionResponse.objects.update_or_create(
question=question,
submission=submission,
- ).save()
+ defaults={"multiple_choice": multiple_choice_obj},
+ )
response = Response(ApplicationQuestionResponseSerializer(obj).data)
- submission.save()
return response
@action(detail=False, methods=["get"])
@@ -5014,10 +5074,10 @@ def questions(self, *args, **kwargs):
response = (
ApplicationQuestionResponse.objects.filter(
- submission__user=self.request.user
+ question=question, submission__user=self.request.user,
)
- .filter(question__prompt=question.prompt)
- .order_by("-updated_at")
+ .select_related("submission", "multiple_choice", "question")
+ .prefetch_related("question__committees", "question__multiple_choice")
.first()
)
@@ -5037,12 +5097,199 @@ class ClubApplicationViewSet(viewsets.ModelViewSet):
create: Create an application for the club.
list: Retrieve a list of applications of the club.
+
+ retrieve: Retrieve information about a single application
+
+ current: Retrieve a list of active applications of the club.
+
+ send_emails: Send out acceptance/rejection emails
"""
permission_classes = [ClubItemPermission | IsSuperuser]
serializer_class = ClubApplicationSerializer
http_method_names = ["get", "post", "put", "patch", "delete"]
+ def destroy(self, *args, **kwargs):
+ """
+ Invalidate cache before deleting
+ """
+ app = self.get_object()
+ key = f"clubapplication:{app.id}"
+ cache.delete(key)
+ return super().destroy(*args, **kwargs)
+
+ def update(self, *args, **kwargs):
+ """
+ Invalidate cache before updating
+ """
+ app = self.get_object()
+ key = f"clubapplication:{app.id}"
+ cache.delete(key)
+ return super().update(*args, **kwargs)
+
+ def retrieve(self, *args, **kwargs):
+ """
+ Cache responses for one hour. This is what people
+ see when viewing an individual club's application
+ """
+
+ pk = self.kwargs["pk"]
+ key = f"clubapplication:{pk}"
+ cached = cache.get(key)
+ if cached:
+ return Response(cached)
+ app = self.get_object()
+ data = ClubApplicationSerializer(app).data
+ cache.set(key, data, 60 * 60)
+ return Response(data)
+
+ @action(detail=True, methods=["post"])
+ def send_emails(self, *args, **kwargs):
+ """
+ Send out acceptance/rejection emails for a particular application
+
+ Dry run will validate that all emails have nonempty variables
+
+ Allow resend will renotify submissions that have already been emailed
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ allow_resend:
+ type: boolean
+ dry_run:
+ type: boolean
+ email_type:
+ type: object
+ properties:
+ id:
+ type: string
+ name:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ detail:
+ type: string
+
+ ---
+
+ """
+
+ app = self.get_object()
+
+ # Query for recent submissions with user and committee joined
+ submissions = ApplicationSubmission.objects.filter(
+ application=app
+ ).select_related("user", "committee")
+
+ dry_run = self.request.data.get("dry_run")
+
+ if not dry_run:
+ # Invalidate submission viewset cache
+ key = f"applicationsubmissions:{app.id}"
+ cache.delete(key)
+
+ email_type = self.request.data.get("email_type")["id"]
+
+ subject = f"Application Update for {app.name}"
+ n, skip = 0, 0
+
+ allow_resend = self.request.data.get("allow_resend")
+
+ acceptance_template = Template(app.acceptance_email)
+ rejection_template = Template(app.rejection_email)
+
+ mass_emails = []
+ for submission in submissions:
+ if (
+ (not allow_resend and submission.notified)
+ or submission.status == ApplicationSubmission.PENDING
+ or not (submission.reason and submission.user.email)
+ ):
+ skip += 1
+ continue
+ elif (
+ submission.status == ApplicationSubmission.ACCEPTED
+ and email_type == "acceptance"
+ ):
+ template = acceptance_template
+ elif (
+ email_type == "rejection"
+ and submission.status != ApplicationSubmission.ACCEPTED
+ ):
+ template = rejection_template
+ else:
+ continue
+
+ data = {
+ "reason": submission.reason,
+ "name": submission.user.first_name or "",
+ "committee": submission.committee.name if submission.committee else "",
+ }
+
+ html_content = template.render(data)
+ text_content = html_to_text(html_content)
+
+ contact_email = app.club.email
+
+ msg = EmailMultiAlternatives(
+ subject,
+ text_content,
+ settings.FROM_EMAIL,
+ [submission.user.email],
+ reply_to=[contact_email],
+ )
+ msg.attach_alternative(html_content, "text/html")
+ mass_emails.append(msg)
+
+ if not dry_run:
+ submission.notified = True
+ n += 1
+
+ if not dry_run:
+ with mail.get_connection() as conn:
+ conn.send_messages(mass_emails)
+ ApplicationSubmission.objects.bulk_update(submissions, ["notified"])
+
+ dry_run_msg = "Would have sent" if dry_run else "Sent"
+ return Response(
+ {
+ "detail": f"{dry_run_msg} emails to {n} people, "
+ f"skipping {skip} due to one of (already notified, no reason, no email)"
+ }
+ )
+
+ @action(detail=False, methods=["get"])
+ def current(self, *args, **kwargs):
+ """
+ Return the ongoing application(s) for this club
+ ---
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/ClubApplication"
+ ---
+ """
+ qs = self.get_queryset().prefetch_related("extensions")
+ now = timezone.now()
+ user = self.request.user
+ q = Q(application_end_time__gte=now)
+ if user.is_authenticated:
+ q |= Q(extensions__end_time__gte=now, extensions__user=user)
+
+ return Response(ClubApplicationSerializer(qs.filter(q), many=True).data)
+
@action(detail=True, methods=["post"])
def duplicate(self, *args, **kwargs):
"""
@@ -5061,6 +5308,7 @@ def duplicate(self, *args, **kwargs):
now = timezone.now()
clone.application_start_time = now + datetime.timedelta(days=1)
clone.application_end_time = now + datetime.timedelta(days=30)
+ clone.result_release_time = now + datetime.timedelta(days=40)
clone.external_url = (
f"https://pennclubs.com/club/{clone.club.code}/" f"application/{clone.pk}"
)
@@ -5069,16 +5317,284 @@ def duplicate(self, *args, **kwargs):
def get_serializer_class(self):
if self.action in {"create", "update", "partial_update"}:
+ if "club_code" in self.kwargs:
+ club = (
+ Club.objects.filter(code=self.kwargs["club_code"])
+ .prefetch_related("badges")
+ .first()
+ )
+ if club and club.is_wharton:
+ return ManagedClubApplicationSerializer
return WritableClubApplicationSerializer
return ClubApplicationSerializer
def get_queryset(self):
- return ClubApplication.objects.filter(club__code=self.kwargs["club_code"])
+ return (
+ ClubApplication.objects.filter(club__code=self.kwargs["club_code"],)
+ .select_related("application_cycle", "club")
+ .prefetch_related(
+ "questions__multiple_choice", "questions__committees", "committees",
+ )
+ )
+
+
+class WhartonCyclesView(viewsets.ModelViewSet):
+ """
+ get: Return information about all Wharton Council application cycles
+ patch: Update application cycle and WC applications with cycle
+ clubs: list clubs with cycle
+ add_clubs: add clubs to cycle
+ remove_clubs_from_all: remove clubs from all cycles
+ """
+
+ permission_classes = [WhartonApplicationPermission | IsSuperuser]
+ # Designed to support partial updates, but ModelForm sends all fields here
+ http_method_names = ["get", "post", "patch", "delete"]
+ serializer_class = ApplicationCycleSerializer
+
+ def get_queryset(self):
+ return ApplicationCycle.objects.all().order_by("end_date")
+
+ def update(self, *args, **kwargs):
+ """
+ Updates times for all applications with cycle
+ """
+ applications = ClubApplication.objects.filter(
+ application_cycle=self.get_object()
+ )
+ str_start_date = self.request.data.get("start_date").replace("T", " ")
+ str_end_date = self.request.data.get("end_date").replace("T", " ")
+ str_release_date = self.request.data.get("release_date").replace("T", " ")
+ time_format = "%Y-%m-%d %H:%M:%S%z"
+ start = (
+ datetime.datetime.strptime(str_start_date, time_format)
+ if str_start_date
+ else self.get_object().start_date
+ )
+ end = (
+ datetime.datetime.strptime(str_end_date, time_format)
+ if str_end_date
+ else self.get_object().end_date
+ )
+ release = (
+ datetime.datetime.strptime(str_release_date, time_format)
+ if str_release_date
+ else self.get_object().release_date
+ )
+ for app in applications:
+ app.application_start_time = start
+ if app.application_end_time_exception:
+ continue
+ app.application_end_time = end
+ app.result_release_time = release
+ f = ["application_start_time", "application_end_time", "result_release_time"]
+ ClubApplication.objects.bulk_update(applications, f)
+ return super().update(*args, **kwargs)
+
+ @action(detail=True, methods=["GET"])
+ def get_clubs(self, *args, **kwargs):
+ """
+ Retrieve clubs associated with given cycle
+ ---
+ requestBody:
+ content: {}
+ responses:
+ "200":
+ content: {}
+ ---
+ """
+ cycle = self.get_object()
+
+ return Response(
+ ClubApplication.objects.filter(application_cycle=cycle)
+ .select_related("club")
+ .values("club__name", "club__code")
+ )
+
+ @action(detail=True, methods=["PATCH"])
+ def edit_clubs(self, *args, **kwargs):
+ """
+ Edit clubs associated with given cycle
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ clubs:
+ type: array
+ items:
+ type: string
+ responses:
+ "200":
+ content: {}
+ ---
+
+ """
+ cycle = self.get_object()
+ club_codes = self.request.data.get("clubs")
+ start = cycle.start_date
+ end = cycle.end_date
+ release = cycle.release_date
+
+ # Some apps get deleted
+ ClubApplication.objects.filter(application_cycle=cycle).exclude(
+ club__code__in=club_codes
+ ).delete()
+
+ # Some apps need to be created - use the default Wharton Template
+ prompt_one = (
+ "Tell us about a time you took " "initiative or demonstrated leadership"
+ )
+ prompt_two = "Tell us about a time you faced a challenge and how you solved it"
+ prompt_three = "Tell us about a time you collaborated well in a team"
+ created_apps_clubs = (
+ ClubApplication.objects.filter(
+ application_cycle=cycle, club__code__in=club_codes
+ )
+ .select_related("club")
+ .values_list("club__code", flat=True)
+ )
+ creation_pending_clubs = Club.objects.filter(
+ code__in=set(club_codes) - set(created_apps_clubs)
+ )
+
+ for club in creation_pending_clubs:
+ name = f"{club.name} Application"
+ most_recent = (
+ ClubApplication.objects.filter(club=club)
+ .order_by("-created_at")
+ .first()
+ )
+
+ if most_recent:
+ # If an application for this club exists, clone it
+ application = most_recent.make_clone()
+ application.application_start_time = start
+ application.application_end_time = end
+ application.result_release_time = release
+ application.application_cycle = cycle
+ application.is_wharton_council = True
+ application.external_url = (
+ f"https://pennclubs.com/club/{club.code}/"
+ f"application/{application.pk}"
+ )
+ application.save()
+ else:
+ # Otherwise, start afresh
+ application = ClubApplication.objects.create(
+ name=name,
+ club=club,
+ application_start_time=start,
+ application_end_time=end,
+ result_release_time=release,
+ application_cycle=cycle,
+ is_wharton_council=True,
+ )
+ external_url = (
+ f"https://pennclubs.com/club/{club.code}/"
+ f"application/{application.pk}"
+ )
+ application.external_url = external_url
+ application.save()
+ prompt = (
+ "Choose one of the following prompts for your personal statement"
+ )
+ prompt_question = ApplicationQuestion.objects.create(
+ question_type=ApplicationQuestion.MULTIPLE_CHOICE,
+ application=application,
+ prompt=prompt,
+ )
+ ApplicationMultipleChoice.objects.create(
+ value=prompt_one, question=prompt_question
+ )
+ ApplicationMultipleChoice.objects.create(
+ value=prompt_two, question=prompt_question
+ )
+ ApplicationMultipleChoice.objects.create(
+ value=prompt_three, question=prompt_question
+ )
+ ApplicationQuestion.objects.create(
+ question_type=ApplicationQuestion.FREE_RESPONSE,
+ prompt="Answer the prompt you selected",
+ word_limit=150,
+ application=application,
+ )
+ return Response([])
-class WhartonApplicationAPIView(generics.ListAPIView):
+ @action(detail=False, methods=["post"])
+ def add_clubs_to_exception(self, *args, **kwargs):
+ """
+ Exempt selected clubs from application cycle deadline
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ clubs:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: integer
+ application_end_time:
+ type: string
+ responses:
+ "200":
+ content: {}
+ ---
+ """
+ clubs = self.request.data.get("clubs")
+ apps = []
+ for club in clubs:
+ app = ClubApplication.objects.get(pk=club["id"])
+ apps.append(app)
+ app.application_end_time = club["end_date"]
+ app.application_end_time_exception = True
+ ClubApplication.objects.bulk_update(
+ apps, ["application_end_time", "application_end_time_exception"],
+ )
+ return Response([])
+
+ @action(detail=False, methods=["post"])
+ def remove_clubs_from_exception(self, *args, **kwargs):
+ """
+ Remove selected clubs from application cycle deadline exemption
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ clubs:
+ type: array
+ items:
+ type: string
+ responses:
+ "200":
+ content: {}
+ ---
+ """
+ club_ids = self.request.data.get("clubs", [])
+ apps = ClubApplication.objects.filter(pk__in=club_ids)
+ for app in apps:
+ app.application_end_time_exception = False
+ app.application_end_time = app.application_cycle.end_date
+ ClubApplication.objects.bulk_update(
+ apps, ["application_end_time", "application_end_time_exception"],
+ )
+ return Response([])
+
+
+class WhartonApplicationAPIView(viewsets.ModelViewSet):
"""
- get: Return information about all Wharton Council club applications which are
+ list: Return information about all Wharton Council club applications which are
currently on going
"""
@@ -5086,31 +5602,38 @@ class WhartonApplicationAPIView(generics.ListAPIView):
serializer_class = ClubApplicationSerializer
def get_operation_id(self, **kwargs):
- return "List Wharton applications and details"
+ return f"{kwargs['operId']} Wharton Application"
def get_queryset(self):
now = timezone.now()
- qs = ClubApplication.objects.filter(
- is_wharton_council=True, application_end_time__gte=now
+ qs = (
+ ClubApplication.objects.filter(
+ is_wharton_council=True,
+ application_start_time__lte=now,
+ application_end_time__gte=now,
+ )
+ .select_related("club")
+ .prefetch_related(
+ "committees", "questions__multiple_choice", "questions__committees"
+ )
)
- # randomly order Wharton Council applications by user
-
+ # Order applications randomly for viewing (consistent and unique per user).
key = str(self.request.user.id)
-
- cached_qs = cache.get(key)
-
- if cached_qs and qs.count() == cached_qs.count():
- return cached_qs
-
qs = qs.annotate(
- random_id=SHA1(Concat("club", Value(key)), output_field=TextField())
- ).order_by("random_id")
- cache.set(key, qs, 60)
-
+ random=SHA1(Concat("name", Value(key), output_field=TextField()))
+ ).order_by("random")
return qs
+ @method_decorator(cache_page(60 * 20))
+ @method_decorator(vary_on_cookie)
+ def list(self, *args, **kwargs):
+ """
+ Cache responses for 20 minutes. Vary cache by user.
+ """
+ return super().list(*args, **kwargs)
+
class WhartonApplicationStatusAPIView(generics.ListAPIView):
"""
@@ -5125,23 +5648,7 @@ def get_operation_id(self, **kwargs):
def get_queryset(self):
return (
- ApplicationSubmission.objects.filter(
- application__is_wharton_council=True,
- created_at__in=RawSQL(
- """SELECT recent_time
- FROM
- (SELECT user_id,
- committee_id,
- application_id,
- max(created_at) recent_time
- FROM clubs_applicationsubmission
- WHERE NOT archived
- GROUP BY user_id,
- committee_id, application_id) recent_subs""",
- (),
- ),
- archived=False,
- )
+ ApplicationSubmission.objects.filter(application__is_wharton_council=True)
.annotate(
annotated_name=F("application__name"),
annotated_committee=F("committee__name"),
@@ -5158,7 +5665,17 @@ def get_queryset(self):
)
-class ApplicationSubmissionViewSet(XLSXFormatterMixin, viewsets.ModelViewSet):
+class ApplicationExtensionViewSet(viewsets.ModelViewSet):
+ permission_classes = [ClubSensitiveItemPermission | IsSuperuser]
+ serializer_class = ApplicationExtensionSerializer
+
+ def get_queryset(self):
+ return ApplicationExtension.objects.filter(
+ application__pk=self.kwargs["application_pk"]
+ )
+
+
+class ApplicationSubmissionViewSet(viewsets.ModelViewSet):
"""
list: List submissions for a given club application.
@@ -5171,35 +5688,50 @@ class ApplicationSubmissionViewSet(XLSXFormatterMixin, viewsets.ModelViewSet):
http_method_names = ["get", "post"]
def get_queryset(self):
- # Use a raw SQL query to obtain the most recent (user, committee) pairs
- # of application submissions for a specific application.
- # Done by grouping by (user_id, commitee_id) and returning the most
- # recent instance in each group, then selecting those instances
+ app_id = self.kwargs["application_pk"]
+ submissions = (
+ ApplicationSubmission.objects.filter(application=app_id)
+ .select_related("user__profile", "committee", "application__club")
+ .prefetch_related(
+ Prefetch(
+ "responses",
+ queryset=ApplicationQuestionResponse.objects.select_related(
+ "multiple_choice", "question"
+ ),
+ ),
+ "responses__question__committees",
+ "responses__question__multiple_choice",
+ )
+ )
+ return submissions
+
+ def list(self, *args, **kwargs):
+ """
+ Manually cache responses (to support invalidation)
+ Responses are invalidated on status / reason updates and email sending
+ """
app_id = self.kwargs["application_pk"]
- query = f"""
- SELECT *
- FROM clubs_applicationsubmission
- WHERE application_id = {app_id}
- AND NOT archived
- AND created_at in
- (SELECT recent_time
- FROM
- (SELECT user_id,
- committee_id,
- max(created_at) recent_time
- FROM clubs_applicationsubmission
- WHERE application_id = {app_id}
- AND NOT archived
- GROUP BY user_id,
- committee_id) recent_subs)
- """
- return ApplicationSubmission.objects.raw(query)
+ key = f"applicationsubmissions:{app_id}"
+ cached = cache.get(key)
+ if cached is not None:
+ return Response(cached)
+ else:
+ serializer = self.get_serializer_class()
+ qs = self.get_queryset()
+ data = serializer(qs, many=True).data
+ cache.set(key, data, 60 * 60)
+
+ return Response(data)
+
+ @method_decorator(cache_page(60 * 60 * 2))
@action(detail=False, methods=["get"])
def export(self, *args, **kwargs):
"""
- Given some application submissions, export them
+ Given some application submissions, export them to CSV.
+
+ Cached for 2 hours.
---
requestBody:
content:
@@ -5213,6 +5745,43 @@ def export(self, *args, **kwargs):
type: integer
status:
type: integer
+ responses:
+ "200":
+ content:
+ text/csv:
+ schema:
+ type: string
+ ---
+ """
+ app_id = int(self.kwargs["application_pk"])
+ data = (
+ ApplicationSubmission.objects.filter(application=app_id)
+ .select_related("user__profile", "committee", "application__club")
+ .prefetch_related(
+ Prefetch(
+ "responses",
+ queryset=ApplicationQuestionResponse.objects.select_related(
+ "multiple_choice", "question"
+ ),
+ ),
+ "responses__question__committees",
+ "responses__question__multiple_choice",
+ )
+ )
+ df = pd.DataFrame(ApplicationSubmissionCSVSerializer(data, many=True).data)
+ resp = HttpResponse(
+ content_type="text/csv",
+ headers={"Content-Disposition": "attachment;filename=submissions.csv"},
+ )
+ df.to_csv(index=True, path_or_buf=resp)
+ return resp
+
+ @action(detail=False, methods=["get"])
+ def exportall(self, *args, **kwargs):
+ """
+ Export all application submissions for a particular cycle
+ ---
+ requestBody: {}
responses:
"200":
content:
@@ -5222,27 +5791,17 @@ def export(self, *args, **kwargs):
properties:
output:
type: string
-
---
"""
+
+ app_id = int(self.kwargs["application_pk"])
+ cycle = ClubApplication.objects.get(id=app_id).application_cycle
data = (
ApplicationSubmission.objects.filter(
application__is_wharton_council=True,
- created_at__in=RawSQL(
- """SELECT recent_time
- FROM
- (SELECT user_id,
- committee_id,
- application_id,
- max(created_at) recent_time
- FROM clubs_applicationsubmission
- WHERE NOT archived
- GROUP BY user_id,
- committee_id, application_id) recent_subs""",
- (),
- ),
- archived=False,
+ application__application_cycle=cycle,
)
+ .select_related("application", "application__application_cycle")
.annotate(
annotated_name=F("application__name"),
annotated_committee=F("committee__name"),
@@ -5284,7 +5843,7 @@ def status(self, *args, **kwargs):
schema:
type: object
properties:
- output:
+ detail:
type: string
---
@@ -5295,10 +5854,83 @@ def status(self, *args, **kwargs):
status in map(lambda x: x[0], ApplicationSubmission.STATUS_TYPES)
and len(submission_pks) > 0
):
- ApplicationSubmission.objects.filter(pk__in=submission_pks).update(
- status=status
+ # Invalidate submission viewset cache
+ submissions = ApplicationSubmission.objects.filter(pk__in=submission_pks)
+ app_id = submissions.first().application.id if submissions.first() else None
+ if not app_id:
+ return Response({"detail": "No submissions found"})
+ key = f"applicationsubmissions:{app_id}"
+ cache.delete(key)
+
+ submissions.update(status=status)
+
+ return Response(
+ {
+ "detail": f"Successfully updated submissions' {submission_pks}"
+ f"status {status}"
+ }
)
- return Response([])
+ else:
+ return Response({"detail": "Invalid request"})
+
+ @action(detail=False, methods=["post"])
+ def reason(self, *args, **kwargs):
+ """
+ Given some application submissions, update their acceptance/rejection
+ reasons
+ ---
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ submissions:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: integer
+ reason:
+ type: string
+
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ detail:
+ type: string
+
+ ---
+ """
+ submissions = self.request.data.get("submissions", [])
+ pks = list(map(lambda x: x["id"], submissions))
+ reasons = list(map(lambda x: x["reason"], submissions))
+
+ submission_objs = ApplicationSubmission.objects.filter(pk__in=pks)
+
+ # Invalidate submission viewset cache
+ app_id = (
+ submission_objs.first().application.id if submission_objs.first() else None
+ )
+ if not app_id:
+ return Response({"detail": "No submissions found"})
+ key = f"applicationsubmissions:{app_id}"
+ cache.delete(key)
+
+ for idx, pk in enumerate(pks):
+ obj = submission_objs.filter(pk=pk).first()
+ if obj:
+ obj.reason = reasons[idx]
+ obj.save()
+ else:
+ return Response({"detail": "Object not found"})
+
+ return Response({"detail": "Successfully updated submissions' reasons"})
def get_serializer_class(self):
if self.request and self.request.query_params.get("format") == "xlsx":
@@ -5319,41 +5951,26 @@ class ApplicationSubmissionUserViewSet(viewsets.ModelViewSet):
http_method_names = ["get", "delete"]
def get_queryset(self):
- distinct_submissions = {}
- submissions = ApplicationSubmission.objects.filter(
- user=self.request.user, archived=False
+ submissions = (
+ ApplicationSubmission.objects.filter(user=self.request.user)
+ .select_related("user__profile", "committee", "application__club")
+ .prefetch_related(
+ Prefetch(
+ "responses",
+ queryset=ApplicationQuestionResponse.objects.select_related(
+ "multiple_choice", "question"
+ ),
+ ),
+ "responses__question__committees",
+ "responses__question__multiple_choice",
+ )
)
-
- # only want to return the most recent (user, committee) unique submission pair
- for submission in submissions:
- key = (submission.application.__str__(), submission.committee.__str__())
- if key in distinct_submissions:
- if distinct_submissions[key].created_at < submission.created_at:
- distinct_submissions[key] = submission
- else:
- distinct_submissions[key] = submission
-
- queryset = ApplicationSubmission.objects.none()
- for submission in distinct_submissions.values():
- queryset |= ApplicationSubmission.objects.filter(pk=submission.pk)
-
- return queryset
-
- def perform_destroy(self, instance):
- """
- Set archived boolean to be True so that the submissions
- appears to have been deleted
- """
-
- instance.archived = True
- instance.archived_by = self.request.user
- instance.archived_on = timezone.now()
- instance.save()
+ return submissions
class ApplicationQuestionViewSet(viewsets.ModelViewSet):
"""
- create: Create a questions for a club application.
+ create: Create a question for a club application.
list: List questions in a given club application.
"""
@@ -5367,6 +5984,54 @@ def get_queryset(self):
application__pk=self.kwargs["application_pk"]
).order_by("precedence")
+ def destroy(self, *args, **kwargs):
+ """
+ Invalidate caches before destroying
+ """
+ app_id = self.kwargs["application_pk"]
+ key1 = f"applicationquestion:{app_id}"
+ key2 = f"clubapplication:{app_id}"
+ cache.delete(key1)
+ cache.delete(key2)
+ return super().destroy(*args, **kwargs)
+
+ def create(self, *args, **kwargs):
+ """
+ Invalidate caches before creating
+ """
+ app_id = self.kwargs["application_pk"]
+ key1 = f"applicationquestion:{app_id}"
+ key2 = f"clubapplication:{app_id}"
+ cache.delete(key1)
+ cache.delete(key2)
+ return super().create(*args, **kwargs)
+
+ def update(self, *args, **kwargs):
+ """
+ Invalidate caches before updating
+ """
+ app_id = self.kwargs["application_pk"]
+ key1 = f"applicationquestion:{app_id}"
+ key2 = f"clubapplication:{app_id}"
+ cache.delete(key1)
+ cache.delete(key2)
+ return super().update(*args, **kwargs)
+
+ def list(self, *args, **kwargs):
+ """
+ Manually cache responses for one hour
+ """
+
+ app_id = self.kwargs["application_pk"]
+ key = f"applicationquestion:{app_id}"
+ cached = cache.get(key)
+ if cached:
+ return Response(cached)
+
+ data = ApplicationQuestionSerializer(self.get_queryset(), many=True).data
+ cache.set(key, data, 60 * 60)
+ return Response(data)
+
@action(detail=False, methods=["post"])
def precedence(self, *args, **kwargs):
"""
diff --git a/backend/pennclubs/settings/base.py b/backend/pennclubs/settings/base.py
index 96ae167b3..e5b379dc5 100644
--- a/backend/pennclubs/settings/base.py
+++ b/backend/pennclubs/settings/base.py
@@ -160,7 +160,7 @@
"DEFAULT_RENDERER_CLASSES": (
"rest_framework.renderers.JSONRenderer",
"rest_framework.renderers.BrowsableAPIRenderer",
- "drf_renderer_xlsx.renderers.XLSXRenderer",
+ "drf_excel.renderers.XLSXRenderer",
),
"DEFAULT_SCHEMA_CLASS": "pennclubs.doc_settings.CustomAutoSchema",
"DEFAULT_AUTHENTICATION_CLASSES": [
diff --git a/backend/pennclubs/settings/production.py b/backend/pennclubs/settings/production.py
index 6ae09c1c0..dbd81fd24 100644
--- a/backend/pennclubs/settings/production.py
+++ b/backend/pennclubs/settings/production.py
@@ -61,7 +61,12 @@
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": f"redis://{REDIS_HOST}:6379/1",
- "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
+ "OPTIONS": {
+ "CLIENT_CLASS": "django_redis.client.DefaultClient",
+ "IGNORE_EXCEPTIONS": True, # ignore Redis connection errors
+ "SOCKET_CONNECT_TIMEOUT": 1,
+ "SOCKET_TIMEOUT": 1,
+ },
"KEY_PREFIX": "django",
}
}
diff --git a/backend/templates/emails/application_extension.html b/backend/templates/emails/application_extension.html
new file mode 100644
index 000000000..f3aeee637
--- /dev/null
+++ b/backend/templates/emails/application_extension.html
@@ -0,0 +1,22 @@
+
+{% extends 'emails/base.html' %}
+
+{% block content %}
+ Application Extension for {{ application_name }}
+ Hello {{ name }},
+ You have been granted an extension for {{ application_name }} by the officers of {{ club }} on Penn Clubs:
+ The updated deadline to submit your application is {{ end_time }}. You can apply using the button below.
+ Apply
+{% endblock %}
diff --git a/backend/templates/emails/renew.html b/backend/templates/emails/renew.html
index b0dbd3c31..38d145f64 100644
--- a/backend/templates/emails/renew.html
+++ b/backend/templates/emails/renew.html
@@ -12,7 +12,7 @@ Renew {{ name }} on Penn Clubs & SAC Fair Signup
Dear {{ name }} officers,
- We have opened the student group registration process for the 2020-2021 academic year. To be
+ We have opened the student group registration process for the current academic year. To be
considered an active student group on campus, the University requires all student organizations
to be registered with the Office of Student Affairs. This year, OSA has migrated the Student
Groups platform from the previous system, G.O. Penn, to Penn Labs' platform, Penn Clubs.
diff --git a/backend/templates/emails/renewal_reminder.html b/backend/templates/emails/renewal_reminder.html
index a701bc094..fe337b736 100644
--- a/backend/templates/emails/renewal_reminder.html
+++ b/backend/templates/emails/renewal_reminder.html
@@ -43,14 +43,14 @@
Reminder to Renew {{ name }} on Penn Clubs & SAC Fair Signup by 8/24!
If you are logged in and do not see any clubs, reply to this email with your PennKey and club name to gain
- access to re-register your club for the 2020-2021 school year.
+ access to re-register your club for the upcoming school year.
Follow the 5 steps listed to update your club's information. This would be the process for your organization to gain
official approval from the Office of Student Affairs. The final page of the renewal process will ask whether
or
- not your club is interested in participating in the Fall 2020 SAC Fair. To sign up, please simply check the box on
+ not your club is interested in participating in the semesterly SAC Fair. To sign up, please simply check the box on
this
page, and your information will be sent to the SAC Fair Coordinator.
@@ -61,4 +61,4 @@ Reminder to Renew {{ name }} on Penn Clubs & SAC Fair Signup by 8/24!
If you have any questions or problems regarding re-registration, please reply to this email. If you have questions
specifically regarding the SAC Fair, please send an email to sacfair@sacfunded.net.
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/backend/tests/clubs/test_views.py b/backend/tests/clubs/test_views.py
index 765e8a714..7646cc6ec 100644
--- a/backend/tests/clubs/test_views.py
+++ b/backend/tests/clubs/test_views.py
@@ -9,6 +9,7 @@
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core import mail
+from django.core.cache import cache
from django.core.management import call_command
from django.test import Client, TestCase
from django.urls import reverse
@@ -1086,6 +1087,7 @@ def test_club_create_description_sanitize_good(self):
},
content_type="application/json",
)
+ cache.clear()
self.assertIn(resp.status_code, [200, 201], resp.content)
resp = self.client.get(reverse("clubs-detail", args=("penn-labs",)))
@@ -2529,71 +2531,3 @@ def test_event_add_meeting(self):
self.event1.refresh_from_db()
self.assertIn("url", resp.data, resp.content)
self.assertTrue(self.event1.url, resp.content)
-
- def test_zoom_webhook(self):
- """
- Test that the Zoom webhook can properly parse a request sent by the Zoom API.
- """
- person = self.user1
- event = self.event1
- meeting_id = "4880003126"
- participant_id = "jswPTE5-StSc-kbAX6n2Rw"
- join_time = "2021-01-10T20:38:49Z"
- leave_time = "2021-01-10T20:42:23Z"
-
- req = {
- "event": "meeting.participant_joined",
- "payload": {
- "object": {
- "participant": {
- "user_id": participant_id,
- "join_time": join_time,
- "email": person.email,
- },
- "id": meeting_id,
- }
- },
- }
-
- resp = self.client.post(
- reverse("webhooks-meeting"), req, content_type="application/json"
- )
- self.assertIn(resp.status_code, [200, 201], resp.content)
- self.assertTrue(
- ZoomMeetingVisit.objects.filter(
- person=person,
- event=event,
- meeting_id=meeting_id,
- participant_id=participant_id,
- join_time=join_time,
- )
- )
-
- req = {
- "event": "meeting.participant_left",
- "payload": {
- "object": {
- "participant": {
- "user_id": participant_id,
- "leave_time": leave_time,
- "email": person.email,
- },
- "id": meeting_id,
- }
- },
- }
-
- resp = self.client.post(
- reverse("webhooks-meeting"), req, content_type="application/json"
- )
- self.assertIn(resp.status_code, [200, 201], resp.content)
- self.assertTrue(
- ZoomMeetingVisit.objects.filter(
- person=person,
- event=event,
- meeting_id=meeting_id,
- participant_id=participant_id,
- join_time=join_time,
- leave_time=leave_time,
- )
- )
diff --git a/frontend/components/Applications.tsx b/frontend/components/Applications.tsx
index a5099030b..0bc7a4b39 100644
--- a/frontend/components/Applications.tsx
+++ b/frontend/components/Applications.tsx
@@ -1,25 +1,84 @@
import { NextPageContext } from 'next'
import Link from 'next/link'
import React, { ReactElement } from 'react'
+import LazyLoad from 'react-lazy-load'
import styled from 'styled-components'
import { Application } from 'types'
import { doBulkLookup } from 'utils'
-import { Card, Text } from '~/components/common'
+import { Text } from '~/components/common'
import { ClubName } from '~/components/EventPage/common'
import DateInterval from '~/components/EventPage/DateInterval'
-import { mediaMaxWidth, mediaMinWidth, PHONE, WHITE } from '~/constants'
+import {
+ ALLBIRDS_GRAY,
+ ANIMATION_DURATION,
+ BORDER_RADIUS,
+ CLUBS_GREY_LIGHT,
+ HOVER_GRAY,
+ mediaMaxWidth,
+ SM,
+ WHITE,
+} from '~/constants'
-const ApplicationCardContainer = styled.div`
- ${mediaMinWidth(PHONE)} {
- max-width: 18em;
- margin: 1rem;
+const CardWrapper = styled.div`
+ ${mediaMaxWidth(SM)} {
+ padding-top: 0;
+ padding-bottom: 1rem;
}
- ${mediaMaxWidth(PHONE)} {
- margin: 1rem 0;
+`
+
+const DescriptionWrapper = styled.p`
+ margin-top: 0.2rem;
+ color: ${CLUBS_GREY_LIGHT};
+ border-top: 1.5px solid rgba(0, 0, 0, 0.05);
+ width: 100%;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 5;
+ overflow: hidden;
+`
+
+const Description = (text) => (
+
+ {text.length > 255 ? (
+
{`${text.substring(0, 255)} ...`}
+ ) : (
+
{text}
+ )}
+
+)
+
+const MainInfo = styled.div`
+ display: flex;
+ flex-direction: row;
+`
+type CardProps = {
+ readonly hovering?: boolean
+ className?: string
+}
+
+const Card = styled.div`
+ padding: 10px;
+ box-shadow: 0 0 0 transparent;
+ transition: all ${ANIMATION_DURATION}ms ease;
+ border-radius: ${BORDER_RADIUS};
+ box-shadow: 0 0 0 ${WHITE};
+ background-color: ${({ hovering }) => (hovering ? HOVER_GRAY : WHITE)};
+ border: 1px solid ${ALLBIRDS_GRAY};
+ justify-content: space-between;
+ height: auto;
+ cursor: pointer;
+
+ &:hover,
+ &:active,
+ &:focus {
+ box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
+ }
+
+ ${mediaMaxWidth(SM)} {
+ width: calc(100%);
+ padding: 8px;
}
- float: left;
- cursor: pointer; !important
`
const Image = styled.img`
@@ -29,35 +88,60 @@ const Image = styled.img`
overflow: hidden;
`
+const AppsContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ min-height: 60vh;
+`
+
function ApplicationsPage({ whartonapplications }): ReactElement {
if ('detail' in whartonapplications) {
return {whartonapplications.detail}
}
return (
- <>
- {whartonapplications != null && whartonapplications.length > 0 ? (
- whartonapplications.map((application) => (
-
-
-
- {application.club_image_url != null &&
- application.club_image_url !== '' && (
-
- )}
-
- {application.name}
-
-
-
- ))
- ) : (
- No applications are currently available.
- )}
- >
+
+
+
)
}
diff --git a/frontend/components/ClubEditPage/ApplicationsCard.tsx b/frontend/components/ClubEditPage/ApplicationsCard.tsx
index 587d733be..1702c39a5 100644
--- a/frontend/components/ClubEditPage/ApplicationsCard.tsx
+++ b/frontend/components/ClubEditPage/ApplicationsCard.tsx
@@ -11,6 +11,7 @@ import {
} from '../../utils/branding'
import { Icon, Modal, Text } from '../common'
import {
+ ApplicationUpdateTextField,
CheckboxField,
CreatableMultipleSelectField,
DateTimeField,
@@ -262,14 +263,23 @@ export default function ApplicationsCard({ club }: Props): ReactElement {
))}
+ {!club.is_wharton && (
+
+ TIP : To copy over your application from last semester, please
+ click duplicate on the application from the season that you
+ would like to copy over and refresh the page. You can then edit this
+ application as you please.
+
+ )}
+
- TIP : To copy over your application from last semester, please
- click duplicate on the application from the season that you
- would like to copy over and refresh the page. You can then edit this
- application as you please.
+ If your club is affiliated with the Wharton Council Centralised
+ Application, please note that editable applications will be provisioned
+ by the system administrator.{' '}
{
@@ -316,19 +326,25 @@ export default function ApplicationsCard({ club }: Props): ReactElement {
name="application_start_time"
as={DateTimeField}
required={true}
- helpText="The date when your application opens."
+ helpText={`The date when your application opens. ${
+ club.is_wharton ? 'Read-only.' : ''
+ }`}
/>
+ Congratulations {{ name }}! You've been accepted to {{ committee }} because {{reason}}!