Skip to content

Commit

Permalink
Merge pull request #301 from clabs/dev
Browse files Browse the repository at this point in the history
misc improvements for voting system
  • Loading branch information
ethrgeist authored Dec 6, 2024
2 parents f46333f + 358a22d commit e5b885f
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 143 deletions.
2 changes: 1 addition & 1 deletion src/rockon/api/permissions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from __future__ import annotations

from .bands import IsCrewReadOnly, IsOwner
from .bands import BookingFieldsPermission, IsCrewReadOnly, IsOwner
28 changes: 28 additions & 0 deletions src/rockon/api/permissions/bands.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,31 @@ def has_object_permission(self, request, view, obj):
and request.method in permissions.SAFE_METHODS
):
return True


class IsInGroupForFields(permissions.BasePermission):
"""
Custom permission to only allow users in specific groups to update certain fields
"""

group_name = None
protected_fields = []

def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True

if request.data:
has_protected_fields = any(
field in request.data for field in self.protected_fields
)

if has_protected_fields:
return request.user.groups.filter(name=self.group_name).exists()

return True


class BookingFieldsPermission(IsInGroupForFields):
group_name = "booking"
protected_fields = ["track", "bid_status"]
7 changes: 5 additions & 2 deletions src/rockon/api/views/bands.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rest_framework.parsers import MultiPartParser
from rest_framework.response import Response

from rockon.api.permissions import IsCrewReadOnly, IsOwner
from rockon.api.permissions import BookingFieldsPermission, IsCrewReadOnly, IsOwner
from rockon.api.serializers import (
BandDetailSerializer,
BandListSerializer,
Expand All @@ -23,7 +23,10 @@ class BandViewSet(viewsets.ModelViewSet):
"""

queryset = Band.objects.all()
permission_classes = [IsCrewReadOnly | permissions.IsAdminUser | IsOwner]
permission_classes = [
IsCrewReadOnly | permissions.IsAdminUser | IsOwner,
BookingFieldsPermission,
]

def get_serializer_class(self):
if self.action == "list":
Expand Down
121 changes: 107 additions & 14 deletions src/rockon/bands/static/js/voting.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,15 @@ const TrackDropdown = Vue.defineComponent({
)
},
template: `
<div>
<select @change="updateSelectedTrack" v-model="selectedBandDetails.track">
<option v-if="!selectedBandDetails.track" disabled v-bind:value="null">Track auswählen</option>
<option v-if="selectedBandDetails.track" value="">Track entfernen</option>
<option v-for="track in tracks" :value="track.id" :key="track.id">
{{ track.name || "Kein Track" }}
</option>
</select>
<div class="form-group">
<label for="trackSelect" class="form-label">Track</label>
<select id="trackSelect" @change="updateSelectedTrack" v-model="selectedBandDetails.track" class="form-control">
<option v-if="!selectedBandDetails.track" disabled v-bind:value="null">Track auswählen</option>
<option v-if="selectedBandDetails.track" value="">Track entfernen</option>
<option v-for="track in tracks" :value="track.id" :key="track.id">
{{ track.name || "Kein Track" }}
</option>
</select>
</div>
`,
methods: {
Expand All @@ -166,6 +167,44 @@ const TrackDropdown = Vue.defineComponent({
}
})

const BidStatusDropdown = Vue.defineComponent({
props: ['bidStates', 'selectedBandDetails'],
emits: ['update:bidStatus'],
template: `
<div class="form-group">
<label for="bidStatusSelect" class="form-label">Status</label>
<select id="bidStatusSelect" @change="updateBidStatus" v-model="selectedBandDetails.bid_status" class="form-control">
<option v-for="(state, index) in bidStates" :key="index" :value="state[0]">
{{ state[1] }}
</option>
</select>
</div>
`,
methods: {
updateBidStatus (event) {
console.debug('BidStatusDropdown updateBidStatus:', event.target.value)
this.$emit('update:bidStatus', event.target.value)
}
}
})

const BackstageLink = Vue.defineComponent({
props: ['selectedBandDetails'],
computed: {
url () {
return `/backstage/rockonbands/band/${this.selectedBandDetails.id}/change/`
}
},
template: `
<div class="form-group">
<label for="backstageBtn" class="form-label">Backstage</label>
<a id="backstageBtn" :href="url" class="btn btn-primary form-control" role="button">
Band bearbeiten
</a>
</div>
`
})

const TrackList = Vue.defineComponent({
props: [
'tracks',
Expand Down Expand Up @@ -519,6 +558,7 @@ const BandRating = Vue.defineComponent({
const BandDetails = Vue.defineComponent({
props: [
'tracks',
'bidStates',
'media',
'federalStates',
'selectedBandDetails',
Expand All @@ -528,6 +568,8 @@ const BandDetails = Vue.defineComponent({
emits: ['update:track', 'update:select-song', 'update:rating'],
components: {
TrackDropdown,
BackstageLink,
BidStatusDropdown,
SongList,
BandImages,
BandDocuments,
Expand Down Expand Up @@ -557,6 +599,9 @@ const BandDetails = Vue.defineComponent({
return 'Kein Cover Letter'
}
return this.selectedBandDetails.cover_letter.replace(/\r\n/g, '<br>')
},
isUnknownOrPending() {
return this.selectedBandDetails.bid_status === 'unknown' || this.selectedBandDetails.bid_status === 'pending';
}
},
template: `
Expand All @@ -565,21 +610,31 @@ const BandDetails = Vue.defineComponent({
<div class="col">
<h3>{{ bandName }}</h3>
</div>
<div v-if="selectedBandDetails.bid_status === 'declined'" class="row mt-2">
<div v-if="isUnknownOrPending" class="row mt-2">
<div class="col">
<div class="alert alert-secondary" role="alert">
<h4 class="alert-heading">Unbearbeitet</h4>
<hr>
<p>Diese Bewerbung wurde vom Band-Gewerk noch nicht gesichtet.</p>
<p>Aus Transparenzgründen ist sie hier gelistet und du kannst ihre Tracks anhören, die Band kann aber nicht bewertet werden.</p>
</div>
</div>
</div>
<div v-if="selectedBandDetails.bid_status === 'declined'" class="row mt-2">
<div class="col">
<div class="alert alert-warning" role="alert">
<h4 class="alert-heading">❌ Bewerbung abgelehnt ❌</h4>
<hr>
<p>Diese Band hat es leider in der Vorauswahl nicht geschafft und die Mindestanforderungen des Bandgewerks erfüllt.</p>
<p> Aus Transparenzgründen ist sie hier gelistet und du kannst ihre Tracks anhören, die Band kann aber nicht bewertet werden.</p>
<p>Diese Band hat es leider in der Vorauswahl nicht geschafft und die organisatorischen Mindestanforderungen an eine Bewerbung erfüllt.</p>
<p>Aus Transparenzgründen ist sie hier gelistet und du kannst ihre Tracks anhören, die Band kann aber nicht bewertet werden.</p>
</div>
</div>
</div>
<div class="row">
<div class="col-9">
<BandTags :selectedBandDetails="selectedBandDetails" :federalStates="federalStates" />
</div>
<div v-if="selectedBandDetails.bid_status !== 'declined'" class="col-3">
<div v-if="selectedBandDetails.bid_status === 'accepted'" class="col-3">
<BandRating :selectedBandDetails="selectedBandDetails" @update:rating="emitRating" />
</div>
</div>
Expand Down Expand Up @@ -619,10 +674,16 @@ const BandDetails = Vue.defineComponent({
</div>
</div>
<div v-if="allowChanges" class="row mb-2">
<h3>Track</h3>
<h3>Verwaltung</h3>
<div class="col">
<TrackDropdown :tracks="tracks" :selectedBandDetails="selectedBandDetails" :currentTrackId="currentTrackId" @update:selectedTrack="updateTrack" />
</div>
<div class="col">
<BidStatusDropdown :bidStates="bidStates" :selectedBandDetails="selectedBandDetails" @update:bidStatus="updateBidStatus" />
</div>
<div class="col">
<BackstageLink :selectedBandDetails="selectedBandDetails" />
</div>
</div>
<div v-if="allowChanges" class="row">
<h4>Booking</h4>
Expand Down Expand Up @@ -658,6 +719,10 @@ const BandDetails = Vue.defineComponent({
console.debug('BandDetails updateTrack:', trackId)
this.$emit('update:track', trackId)
},
updateBidStatus (bidStatus) {
console.debug('BandDetails updateBidStatus:', bidStatus)
this.$emit('update:bidStatus', bidStatus)
},
handleSongSelect (song) {
// Update the data with the selected song
console.debug('BandDetails handleSongSelect:', song)
Expand All @@ -671,7 +736,7 @@ const BandDetails = Vue.defineComponent({
console.debug('BandDetails emitRating:', rating)
this.$emit('update:rating', rating)
}
}
},
})

const app = createApp({
Expand All @@ -686,6 +751,7 @@ const app = createApp({
tracks: window.rockon_data.tracks,
bands: [],
federalStates: window.rockon_data.federal_states,
bidStates: window.rockon_data.bid_states,
selectedTrack: null,
selectedBand: null,
userVotes: window.rockon_data.user_votes,
Expand All @@ -709,6 +775,7 @@ const app = createApp({
BandList,
BandDetails,
TrackDropdown,
BidStatusDropdown,
SongList,
SongInfo,
BandImages,
Expand Down Expand Up @@ -812,6 +879,32 @@ const app = createApp({
this.selectedBandDetails.track = trackId
this.selectedTrack = track
},
updateBidStatus (bidStatus) {
api_url = window.rockon_api.update_band.replace(
'pk_placeholder',
this.selectedBandDetails.id
)
console.debug(
'Selected band:',
this.selectedBandDetails.id,
bidStatus,
api_url
)
fetch(api_url, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.crsf_token
},
body: JSON.stringify({
bid_status: bidStatus
})
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error))
this.selectedBandDetails.bid_state = bidStatus
},
handleSongSelect (song) {
console.debug('app handleSongSelect:', song)
if (this.playSong === song) {
Expand Down
Loading

0 comments on commit e5b885f

Please sign in to comment.