Skip to content

Commit

Permalink
merge with master
Browse files Browse the repository at this point in the history
  • Loading branch information
owlester12 committed Nov 3, 2024
2 parents ef2ad16 + 3a002c4 commit 864f222
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 386 deletions.
36 changes: 36 additions & 0 deletions backend/clubs/management/commands/osa_perms_updates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand

from clubs.models import Club


class Command(BaseCommand):
help = "Give superuser to hard-coded user accounts affiliated with OSA."
web_execute = True

def handle(self, *args, **kwargs):
User = get_user_model()
content_type = ContentType.objects.get_for_model(Club)
approve_perm = Permission.objects.get(
codename="approve_club", content_type=content_type
)
pending_perm = Permission.objects.get(
codename="see_pending_clubs", content_type=content_type
)
if not settings.OSA_KEYS:
raise ValueError("OSA_KEYS not set in settings")
if not (approvers := Group.objects.filter(name="Approvers").first()):
raise ValueError("Approvers group not found")
for key in settings.OSA_KEYS:
if not key or not (user := User.objects.get(username=key)):
continue
user.is_superuser = True
user.is_staff = True
user.user_permissions.add(approve_perm)
user.user_permissions.add(pending_perm)
approvers.user_set.add(user)
user.save()
approvers.save()
51 changes: 50 additions & 1 deletion backend/clubs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6562,6 +6562,52 @@ def remove_clubs_from_exception(self, *args, **kwargs):
)
return Response([])

@action(detail=True, methods=["GET"])
def club_applications(self, *args, **kwargs):
"""
Retrieve club applications for given cycle
---
requestBody:
content: {}
responses:
"200":
content:
application/json:
schema:
type: array
items:
type: object
properties:
name:
type: string
id:
type: integer
application_end_time:
type: string
format: date-time
application_end_time_exception:
type: string
club__name:
type: string
club__code:
type: string
---
"""
cycle = self.get_object()

return Response(
ClubApplication.objects.filter(application_cycle=cycle)
.select_related("club")
.values(
"name",
"id",
"application_end_time",
"application_end_time_exception",
"club__name",
"club__code",
)
)

@action(detail=True, methods=["GET"])
def applications(self, *args, **kwargs):
"""
Expand All @@ -6570,7 +6616,10 @@ def applications(self, *args, **kwargs):
requestBody: {}
responses:
"200":
content: {}
content:
text/csv:
schema:
type: string
---
"""
cycle = self.get_object()
Expand Down
2 changes: 2 additions & 0 deletions backend/pennclubs/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,5 @@

# Cybersource settings
CYBERSOURCE_CLIENT_VERSION = "0.15"

OSA_KEYS = None
2 changes: 2 additions & 0 deletions backend/pennclubs/settings/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@
"run_environment": "apitest.cybersource.com",
}
CYBERSOURCE_TARGET_ORIGIN = "https://localhost:3001"

OSA_KEYS = ["gwashington"]
2 changes: 2 additions & 0 deletions backend/pennclubs/settings/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,5 @@
"run_environment": "api.cybersource.com",
}
CYBERSOURCE_TARGET_ORIGIN = "https://pennclubs.com"

OSA_KEYS = os.getenv("OSA_KEYS", "").split(",")
28 changes: 28 additions & 0 deletions backend/tests/clubs/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,3 +777,31 @@ def test_graduate_users_output(self):
"Updated the membership status of 1 student club relationships!",
out.getvalue(),
)


class OsaPermsUpdatesTestCase(TestCase):
def setUp(self):
self.user1 = get_user_model().objects.create_user("gwashington")

def test_osa_perms_updates(self):
# Test error when OSA_KEYS is not set
with mock.patch("django.conf.settings.OSA_KEYS", None):
with self.assertRaises(ValueError):
call_command("osa_perms_updates")
self.assertFalse(self.user1.is_superuser)

with mock.patch("django.conf.settings.OSA_KEYS", ["gwashington"]):
# Test error when Approvers group is not found
with self.assertRaises(ValueError):
call_command("osa_perms_updates")
self.assertFalse(self.user1.is_superuser)

# Create Approvers group
Group.objects.create(name="Approvers")
call_command("osa_perms_updates")
self.user1.refresh_from_db()
self.assertTrue(self.user1.groups.filter(name="Approvers").exists())
self.assertTrue(self.user1.is_staff)
self.assertTrue(self.user1.is_superuser)
self.assertTrue(self.user1.has_perm("approve_club"))
self.assertTrue(self.user1.has_perm("see_pending_clubs"))
54 changes: 54 additions & 0 deletions frontend/components/ClubEditPage/ClubEditCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
SITE_ID,
SITE_NAME,
} from '../../utils/branding'
import { ModalContent } from '../ClubPage/Actions'
import { LiveBanner, LiveSub, LiveTitle } from '../ClubPage/LiveEventsDialog'
import { Checkbox, CheckboxLabel, Contact, Modal, Text } from '../common'
import {
Expand Down Expand Up @@ -226,6 +227,7 @@ export default function ClubEditCard({
isEdit,
onSubmit = () => Promise.resolve(undefined),
}: ClubEditCardProps): ReactElement {
const [showRankModal, setShowRankModal] = useState<boolean>(false)
const [showTargetFields, setShowTargetFields] = useState<boolean>(
!!(
club.target_majors?.length ||
Expand Down Expand Up @@ -436,6 +438,58 @@ export default function ClubEditCard({
{
name: 'General',
type: 'group',
description: (
<div className="mb-4">
<a onClick={() => setShowRankModal(true)}>
How does filling out this information affect your club?
</a>
<Modal
show={showRankModal}
closeModal={() => setShowRankModal(false)}
marginBottom={false}
width="80%"
>
<ModalContent className="content mb-4">
<h2>How we calculate club rankings</h2>
<hr />
<h5>
The following positively affects your club's ranking in homepage
search results:
</h5>
<ul>
<li>
Upcoming events with filled out name, description, and image
</li>
<li>Upcoming, open applications for membership</li>
<li>
Having at least 3 active officers, plus a bonus for any
additional non-officer member on the platform
</li>
<li>
Having between 3 and 7 useful tags (please email <Contact />{' '}
if none apply)
</li>
<li>
Posting a public (non-personal) contact email and 2 or more
social links
</li>
<li>
Having a club logo image uploaded and subtitle filled out
</li>
<li>
Filling out a club mission with images and detail (rewarded up
to 1000 words)
</li>
<li>Displaying 3 or more student testimonials (experiences)</li>
<li>Filling out the {FIELD_PARTICIPATION_LABEL} section</li>
<li>
Updating the club listing recently (within the last 8 months)
</li>
</ul>
</ModalContent>
</Modal>
</div>
),
fields: [
{
name: 'name',
Expand Down
81 changes: 61 additions & 20 deletions frontend/components/Settings/WhartonApplicationCycles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ import ModelForm from '../ModelForm'

const fields = (
<>
<Field name="name" as={TextField} />
<Field name="start_date" as={DateTimeField} />
<Field name="end_date" as={DateTimeField} />
<Field name="release_date" as={DateTimeField} />
<Field name="name" as={TextField} required />
<Field name="start_date" as={DateTimeField} required />
<Field name="end_date" as={DateTimeField} required />
<Field name="release_date" as={DateTimeField} required />
</>
)

type Cycle = {
name: string
id: number | null
endDate: Date
}

type ClubOption = {
Expand All @@ -35,7 +36,8 @@ type ExtensionOption = {
clubName: string
endDate: Date
exception?: boolean
changed: boolean
originalEndDate: Date
originalException: boolean
}

const ScrollWrapper = styled.div`
Expand All @@ -44,17 +46,24 @@ const ScrollWrapper = styled.div`
height: 40vh;
`

type ClubApplicationWithClub = ClubApplication & {
club__name: string
club__code: number
}

const WhartonApplicationCycles = (): ReactElement => {
const [editMembership, setEditMembership] = useState(false)
const [membershipCycle, setMembershipCycle] = useState<Cycle>({
name: '',
id: null,
endDate: new Date(),
})

const [editExtensions, setEditExtensions] = useState(false)
const [extensionsCycle, setExtensionsCycle] = useState<Cycle>({
name: '',
id: null,
endDate: new Date(),
})

const [clubsSelectedMembership, setClubsSelectedMembership] = useState<
Expand All @@ -81,7 +90,10 @@ const WhartonApplicationCycles = (): ReactElement => {
const closeExtensionsModal = (): void => {
setEditExtensions(false)
// calculate clubs that have changed
const clubsToUpdate = clubsExtensions.filter((x) => x.changed)
const clubsToUpdate = clubsExtensions.filter(
(x) =>
x.originalEndDate !== x.endDate || x.originalException !== x.exception,
)
// split into clubs with exceptions and clubs without
const clubsExceptions = clubsToUpdate.filter((x) => x.exception)
const clubsNoExceptions = clubsToUpdate.filter((x) => !x.exception)
Expand Down Expand Up @@ -147,18 +159,23 @@ const WhartonApplicationCycles = (): ReactElement => {

useEffect(() => {
if (extensionsCycle && extensionsCycle.id != null) {
doApiRequest(`/cycles/${extensionsCycle.id}/clubs?format=json`)
doApiRequest(
`/cycles/${extensionsCycle.id}/club_applications?format=json`,
)
.then((resp) => resp.json())
.then((data) => {
const initialOptions = data.map((club: ClubApplication) => {
return {
id: club.id,
clubName: club.name,
endDate: new Date(club.application_end_time),
exception: club.application_end_time_exception,
changed: false,
}
})
const initialOptions = data.map(
(application: ClubApplicationWithClub) => {
return {
id: application.id,
clubName: application.club__name,
endDate: new Date(application.application_end_time),
exception: application.application_end_time_exception,
originalEndDate: new Date(application.application_end_time),
originalException: application.application_end_time_exception,
}
},
)
setClubsExtensions(initialOptions)
})
}
Expand Down Expand Up @@ -190,7 +207,11 @@ const WhartonApplicationCycles = (): ReactElement => {
<button
className="button is-info is-small"
onClick={() => {
setMembershipCycle({ name: object.name, id: object.id })
setMembershipCycle({
name: object.name,
id: object.id,
endDate: new Date(object.end_date),
})
setEditMembership(true)
setEditExtensions(false)
}}
Expand All @@ -200,7 +221,11 @@ const WhartonApplicationCycles = (): ReactElement => {
<button
className="button is-info is-small"
onClick={() => {
setExtensionsCycle({ name: object.name, id: object.id })
setExtensionsCycle({
name: object.name,
id: object.id,
endDate: new Date(object.end_date),
})
setEditExtensions(true)
setEditMembership(false)
}}
Expand Down Expand Up @@ -290,7 +315,6 @@ const WhartonApplicationCycles = (): ReactElement => {
selected={club.endDate}
onChange={(date) => {
club.endDate = date
club.changed = true
setClubsExtensions([...clubsExtensions])
}}
/>
Expand All @@ -299,7 +323,6 @@ const WhartonApplicationCycles = (): ReactElement => {
<Checkbox
onChange={(e) => {
club.exception = e.target.checked
club.changed = true
setClubsExtensions([...clubsExtensions])
}}
checked={
Expand All @@ -320,9 +343,27 @@ const WhartonApplicationCycles = (): ReactElement => {
className="button is-primary"
style={{ position: 'absolute', bottom: 10, right: 10 }}
onClick={closeExtensionsModal}
disabled={clubsExtensions.some(
(x) =>
// For the case where we change end date without giving an exception to a club without one
!x.exception &&
!x.originalException &&
x.endDate.getTime() !== extensionsCycle.endDate.getTime(),
)}
>
Submit
</button>
{clubsExtensions.some(
(x) =>
!x.exception &&
!x.originalException &&
x.endDate.getTime() !== extensionsCycle.endDate.getTime(),
) && (
<p className="is-danger">
To change the end date for a club, you must also check its
exception box.
</p>
)}
</>
)}
</Modal>
Expand Down
Loading

0 comments on commit 864f222

Please sign in to comment.