-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/ownership requests #740
base: master
Are you sure you want to change the base?
Changes from all commits
42e953b
9439212
32baced
38224e3
dc828b2
5fba7b8
6b511f0
f36d1f9
c5b6c4c
917c1fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Generated by Django 5.0.4 on 2024-10-18 05:04 | ||
|
||
import django.db.models.deletion | ||
from django.conf import settings | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("clubs", "0117_clubapprovalresponsetemplate"), | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="OwnershipRequest", | ||
fields=[ | ||
("id", models.AutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID")), | ||
("withdrew", models.BooleanField(default=False)), | ||
("created_at", models.DateTimeField(auto_now_add=True)), | ||
("updated_at", models.DateTimeField(auto_now=True)), | ||
("club", models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to="clubs.club")), | ||
("person", models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to=settings.AUTH_USER_MODEL)), | ||
], | ||
options={ | ||
"unique_together": {("person", "club")}, | ||
}, | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Generated by Django 5.0.4 on 2024-10-18 11:42 | ||
|
||
import django.db.models.deletion | ||
from django.conf import settings | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("clubs", "0118_ownershiprequest"), | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
] | ||
|
||
operations = [ | ||
migrations.RenameField( | ||
model_name="ownershiprequest", | ||
old_name="withdrew", | ||
new_name="withdrawn", | ||
), | ||
migrations.RenameField( | ||
model_name="ownershiprequest", | ||
old_name="person", | ||
new_name="requester", | ||
), | ||
migrations.AlterField( | ||
model_name="ownershiprequest", | ||
name="club", | ||
field=models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
related_name="ownership_requests", | ||
to="clubs.club" | ||
), | ||
), | ||
migrations.AlterField( | ||
model_name="ownershiprequest", | ||
name="requester", | ||
field=models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
related_name="ownership_requests", | ||
to=settings.AUTH_USER_MODEL | ||
), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -437,6 +437,26 @@ | |
return membership is not None and membership.role <= Membership.ROLE_OFFICER | ||
|
||
|
||
class OwnershipRequestPermission(permissions.BasePermission): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice work! |
||
""" | ||
Only owners can view and modify ownership requests. | ||
""" | ||
|
||
def has_permission(self, request, view): | ||
if not request.user.is_authenticated: | ||
return False | ||
|
||
if "club_code" not in view.kwargs: | ||
return False | ||
|
||
if request.user.has_perm("clubs.manage_club"): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm not mistaken, this will allow officers to handle ownership requests. See here |
||
return True | ||
|
||
obj = Club.objects.get(code=view.kwargs["club_code"]) | ||
membership = find_membership_helper(request.user, obj) | ||
return membership is not None and membership.role == Membership.ROLE_OWNER | ||
|
||
|
||
class InvitePermission(permissions.BasePermission): | ||
""" | ||
Officers and higher can list/delete invitations. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,7 @@ | |
MembershipRequest, | ||
Note, | ||
NoteTag, | ||
OwnershipRequest, | ||
Profile, | ||
QuestionAnswer, | ||
Report, | ||
|
@@ -1999,6 +2000,72 @@ | |
fields = ("club", "club_name", "person") | ||
|
||
|
||
class OwnershipRequestSerializer(serializers.ModelSerializer): | ||
""" | ||
Used by club owners to see who has requested to be owner of the club. | ||
""" | ||
|
||
requester = serializers.HiddenField(default=serializers.CurrentUserDefault()) | ||
club = serializers.SlugRelatedField(queryset=Club.objects.all(), slug_field="code") | ||
name = serializers.SerializerMethodField("get_full_name") | ||
username = serializers.CharField(source="requester.username", read_only=True) | ||
email = serializers.EmailField(source="requester.email", read_only=True) | ||
|
||
school = SchoolSerializer( | ||
many=True, source="requester.profile.school", read_only=True | ||
) | ||
major = MajorSerializer(many=True, source="requester.profile.major", read_only=True) | ||
graduation_year = serializers.IntegerField( | ||
source="requester.profile.graduation_year", read_only=True | ||
) | ||
|
||
def get_full_name(self, obj): | ||
return obj.requester.get_full_name() | ||
|
||
class Meta: | ||
model = OwnershipRequest | ||
fields = ( | ||
"club", | ||
"created_at", | ||
"email", | ||
"graduation_year", | ||
"major", | ||
"name", | ||
"requester", | ||
"school", | ||
"username", | ||
) | ||
validators = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is redundant as we're already enforcing it at the database level |
||
validators.UniqueTogetherValidator( | ||
queryset=OwnershipRequest.objects.all(), fields=["club", "requester"] | ||
) | ||
] | ||
|
||
|
||
class UserOwnershipRequestSerializer(serializers.ModelSerializer): | ||
""" | ||
Used by the users to return the clubs that the user has sent OwnershipRequest to. | ||
""" | ||
|
||
requester = serializers.HiddenField(default=serializers.CurrentUserDefault()) | ||
club = serializers.SlugRelatedField(queryset=Club.objects.all(), slug_field="code") | ||
club_name = serializers.CharField(source="club.name", read_only=True) | ||
|
||
def create(self, validated_data): | ||
""" | ||
Send an email when a ownership request is created. | ||
""" | ||
obj = super().create(validated_data) | ||
|
||
obj.send_request(self.context["request"]) | ||
|
||
return obj | ||
|
||
class Meta: | ||
model = OwnershipRequest | ||
fields = ("club", "club_name", "requester") | ||
|
||
|
||
class MinimalUserProfileSerializer(serializers.ModelSerializer): | ||
""" | ||
A profile serializer used for the list view of all users. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,8 @@ | |
MemberViewSet, | ||
NoteViewSet, | ||
OptionListView, | ||
OwnershipRequestManagementViewSet, | ||
OwnershipRequestViewSet, | ||
QuestionAnswerViewSet, | ||
ReportViewSet, | ||
SchoolViewSet, | ||
|
@@ -70,7 +72,10 @@ | |
router.register(r"clubvisits", ClubVisitViewSet, basename="clubvisits") | ||
router.register(r"searches", SearchQueryViewSet, basename="searches") | ||
router.register(r"memberships", MembershipViewSet, basename="members") | ||
router.register(r"requests", MembershipRequestViewSet, basename="requests") | ||
router.register(r"requests/membership", MembershipRequestViewSet, basename="requests") | ||
router.register( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that we have requests for both ownership and membership, might be better to have the routes as |
||
r"requests/ownership", OwnershipRequestViewSet, basename="ownershiprequests" | ||
) | ||
router.register(r"tickets", TicketViewSet, basename="tickets") | ||
|
||
router.register(r"schools", SchoolViewSet, basename="schools") | ||
|
@@ -108,6 +113,11 @@ | |
MembershipRequestOwnerViewSet, | ||
basename="club-membership-requests", | ||
) | ||
clubs_router.register( | ||
r"ownershiprequests", | ||
OwnershipRequestManagementViewSet, | ||
basename="club-ownership-requests", | ||
) | ||
clubs_router.register(r"advisors", AdvisorViewSet, basename="club-advisors") | ||
clubs_router.register( | ||
r"applications", ClubApplicationViewSet, basename="club-applications" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Realized that you probably used
MembershipRequest
as a reference, feel free to make the suggested changes there as well