Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions src/backoffice/templates/feedback_detail.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
{% extends 'base.html' %}

{% block title %}
Event Feedback Detail | Orga | BackOffice | {{ block.super }}
Camp Feedback Detail | Orga | BackOffice | {{ block.super }}
{% endblock %}

{% block content %}
<div class="card">
<div class="card-header">
<h3 class="card-title">Event Feedback detail</h3>
<h3 class="card-title">
Camp Feedback detail
<span
{% if feedback.state == "spam" %}
class="badge text-bg-warning"
{% elif feedback.state == "reviewed" %}
class="badge text-bg-success"
{% else %}
class="badge text-bg-primary"
{% endif %}
>
{{ feedback.get_state_display }}</span>
</h3>
</div>
<div class="card-body">
<div class="mb-4">
<figure>
<blockquote class="blockquote">
<p>{{ feedback.feedback }}</p>
{{ feedback.feedback|linebreaks }}
</blockquote>
<figcaption class="blockquote-footer">
<cite title="Source Title">{{ feedback.user.profile.public_credit_name }}</cite>
Expand All @@ -23,8 +35,11 @@ <h3 class="card-title">Event Feedback detail</h3>
<div class="d-flex justify-content-start">
<a class="btn btn-secondary me-2" href="{% url 'backoffice:index' camp_slug=camp.slug %}#orga"><i class="fas fa-undo"></i> Backoffice</a>
<a class="btn btn-primary me-2" href="{% url 'backoffice:feedback_list' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Feedback list</a>
{% if not feedback.processed_by %}
<a href="{% url 'backoffice:feedback_process' camp_slug=camp.slug pk=feedback.pk %}" class="btn btn-sm btn-success"><i class="fas fa-check"></i> Processed</a>
{% if feedback.state == 'unprocessed' %}
<a href="{% url 'backoffice:feedback_process' camp_slug=camp.slug pk=feedback.pk state='reviewed' %}" class="btn btn-success"><i class="fas fa-check"></i> Reviewed</a>
<a href="{% url 'backoffice:feedback_process' camp_slug=camp.slug pk=feedback.pk state='spam' %}" class="btn btn-warning"><i class="fa-solid fa-fire"></i> Spam</a>
{% else %}
<a href="{% url 'backoffice:feedback_process' camp_slug=camp.slug pk=feedback.pk state='unprocessed' %}" class="btn btn-info"><i class="fa-solid fa-arrows-rotate"></i> Unprocess</a>
{% endif %}
</div>
</div>
Expand Down
25 changes: 19 additions & 6 deletions src/backoffice/templates/feedback_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
{% load imageutils %}

{% block title %}
Event Feedback List | {{ block.super }}
Camp Feedback List | {{ block.super }}
{% endblock %}

{% block content %}
<div class="row">
<h2>Event Feedback List</h2>
<h2>Camp Feedback List</h2>
<div>
<p class="lead">List of all feedbacks for this camp </p>
<a class="btn btn-secondary" href="{% url 'backoffice:index' camp_slug=camp.slug %}#orga"><i class="fas fa-undo"></i> Backoffice</a>
Expand All @@ -23,6 +23,7 @@ <h2>Event Feedback List</h2>
<th>Username</th>
<th>Public Credit Name</th>
<th>Feedback</th>
<th>State</th>
<th>Processed at</th>
<th>Processed by</th>
<th>Actions</th>
Expand All @@ -33,13 +34,25 @@ <h2>Event Feedback List</h2>
<tr>
<td>{{ feedback.user.username }}</td>
<td>{{ feedback.user.profile.public_credit_name }}</td>
<td>{{ feedback.feedback|truncatewords:12 }}</td>
<td>{{ feedback.feedback|truncatewords:25 }}</td>
<td>
<span
{% if feedback.state == "spam" %}
class="badge text-bg-warning"
{% elif feedback.state == "reviewed" %}
class="badge text-bg-success"
{% else %}
class="badge text-bg-primary"
{% endif %}
>
{{ feedback.get_state_display }}</span></td>
<td>{{ feedback.processed_at|default_if_none:"" }}</td>
<td>{{ feedback.processed_by|default_if_none:"" }}</td>
<td>
<a href="{% url 'backoffice:feedback_detail' camp_slug=camp.slug pk=feedback.pk %}" class="btn btn-sm btn-primary">Detail</a>
{% if not feedback.processed_by %}
<a href="{% url 'backoffice:feedback_process' camp_slug=camp.slug pk=feedback.pk %}" class="btn btn-sm btn-success"><i class="fas fa-check"></i> Processed</a>
<a href="{% url 'backoffice:feedback_detail' camp_slug=camp.slug pk=feedback.pk %}" class="btn btn-sm btn-secondary">Detail</a>
{% if feedback.state == 'unprocessed' %}
<a href="{% url 'backoffice:feedback_process' camp_slug=camp.slug pk=feedback.pk state='reviewed' %}" class="btn btn-sm btn-success"><i class="fas fa-check"></i> Reviewed</a>
<a href="{% url 'backoffice:feedback_process' camp_slug=camp.slug pk=feedback.pk state='spam' %}" class="btn btn-sm btn-warning"><i class="fa-solid fa-fire"></i> Spam</a>
{% endif %}
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{% extends 'base.html' %}

{% block title %}
Confirm Event Feedback Processed | {{ block.super }}
Confirm Camp Feedback Processed | {{ block.super }}
{% endblock %}

{% block content %}
<div class="card">
<div class="card-header">
<h3 class="card-title">Mark event feedback as processed</h3>
<h3 class="card-title">Mark event feedback as {{ state }}</h3>
</div>
<div class="card-body">
<div class="mb-4">
<figure>
<blockquote class="blockquote">
<p>{{ feedback.feedback }}</p>
{{ feedback.feedback|linebreaks }}
</blockquote>
<figcaption class="blockquote-footer">
<cite title="Source Title">{{ feedback.user.profile.public_credit_name }}</cite>
Expand All @@ -22,7 +22,7 @@ <h3 class="card-title">Mark event feedback as processed</h3>
</div>
<div class="d-flex justify-content-start">
<a class="btn btn-secondary me-2" href="{% url 'backoffice:feedback_list' camp_slug=camp.slug %}"><i class="fas fa-ban"></i> Cancel</a>
<form method="post" action="{% url 'backoffice:feedback_process' camp_slug=camp.slug pk=feedback.pk %}">
<form method="post" action="{% url 'backoffice:feedback_process' camp_slug=camp.slug pk=feedback.pk state=state %}">
{% csrf_token %}
<button type="submit" class="btn btn-success"><i class="fas fa-check"></i> Confirm</a>
</form>
Expand Down
2 changes: 1 addition & 1 deletion src/backoffice/templates/includes/index_orga.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ <h4 class="list-group-item-heading">Approve Public Credit Names</h4>
<p class="list-group-item-text">Use this view to check and approve users Public Credit Names</p>
</a>
<a href="{% url 'backoffice:feedback_list' camp_slug=camp.slug %}" class="list-group-item list-group-item-action">
<h4 class="list-group-item-heading">Event Feedback List</h4>
<h4 class="list-group-item-heading">Camp Feedback List</h4>
<p class="list-group-item-text">View all of the feedbacks for this camp</p>
</a>
<a href="{% url 'backoffice:merchandise_orders' camp_slug=camp.slug %}" class="list-group-item list-group-item-action">
Expand Down
79 changes: 79 additions & 0 deletions src/backoffice/tests/test_orga.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

from django.urls import reverse
from feedback.models import CampFeedback
from utils.tests import BornhackTestBase


class TestCampFeedbackProcessView(BornhackTestBase):
"""Test CampFeedbackProcessView."""

@classmethod
def setUpTestData(cls) -> None:
"""Test setup."""
super().setUpTestData()
cls.feedback = CampFeedback.objects.create(
camp=cls.camp,
user=cls.users[0],
feedback="Test Feedback"
)

cls.admin = cls.users["admin"]
cls.kwargs = {
"camp_slug": cls.camp.slug,
"pk": cls.feedback.pk,
"state": "reviewed"
}

def test_admin_processing_feedback_as_reviewed(self) -> None:
"""Test admin user processing feedback as reviewed."""
self.client.force_login(self.admin)
url = reverse("backoffice:feedback_process", kwargs=self.kwargs)

self.client.post(url)
self.feedback.refresh_from_db()

assert self.feedback.state == CampFeedback.StateChoices.REVIEWED

def test_admin_processing_feedback_as_spam(self) -> None:
"""Test admin user processing feedback as spam."""
self.client.force_login(self.admin)
self.kwargs.update({"state": "spam"})
url = reverse("backoffice:feedback_process", kwargs=self.kwargs)

self.client.post(url)
self.feedback.refresh_from_db()

assert self.feedback.state == CampFeedback.StateChoices.SPAM

def test_admin_resets_feedback_as_unprocessed(self) -> None:
"""Test admin user resets feedback as unprocessed."""
self.client.force_login(self.admin)
self.kwargs.update({"state": "unprocessed"})
url = reverse("backoffice:feedback_process", kwargs=self.kwargs)

self.client.post(url)
self.feedback.refresh_from_db()

assert self.feedback.state == CampFeedback.StateChoices.UNPROCESSED

def test_bad_request_processing_feedback_with_invalid_state(self) -> None:
"""
Test processing feedback with invalid state return BadRequest.
"""
self.client.force_login(self.admin)
self.kwargs.update({"state": "unknown"})
url = reverse("backoffice:feedback_process", kwargs=self.kwargs)

response = self.client.post(url)

assert response.status_code == 400

def test_bad_request_requesting_view_with_invalid_state(self) -> None:
"""Test `GET` request to view with invalid state return BadRequest."""
self.client.force_login(self.admin)
self.kwargs.update({"state": "unknown"})
url = reverse("backoffice:feedback_process", kwargs=self.kwargs)

response = self.client.get(url)

assert response.status_code == 400
12 changes: 6 additions & 6 deletions src/backoffice/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@
from .views import FacilityTypeListView
from .views import FacilityTypeUpdateView
from .views import FacilityUpdateView
from .views import EventFeedbackDetailView
from .views import EventFeedbackListView
from .views import EventFeedbackProcessView
from .views import CampFeedbackDetailView
from .views import CampFeedbackListView
from .views import CampFeedbackProcessView
from .views import InvoiceDownloadMultipleView
from .views import InvoiceDownloadView
from .views import InvoiceListCSVView
Expand Down Expand Up @@ -422,9 +422,9 @@
),
),
# feedback
path("feedback_list/", EventFeedbackListView.as_view(), name="feedback_list"),
path("feedback_detail/<uuid:pk>/", EventFeedbackDetailView.as_view(), name="feedback_detail"),
path("feedback_process/<uuid:pk>/", EventFeedbackProcessView.as_view(), name="feedback_process"),
path("feedback_list/", CampFeedbackListView.as_view(), name="feedback_list"),
path("feedback_detail/<uuid:pk>/", CampFeedbackDetailView.as_view(), name="feedback_detail"),
path("feedback_process/<uuid:pk>/<str:state>", CampFeedbackProcessView.as_view(), name="feedback_process"),
# infodesk
path(
"infodesk/",
Expand Down
48 changes: 34 additions & 14 deletions src/backoffice/views/orga.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.contrib import messages
from django.forms import modelformset_factory
from django.http import HttpResponseRedirect
from django.http.request import BadRequest
from django.shortcuts import redirect
from django.shortcuts import render
from django.urls import reverse
Expand All @@ -20,7 +21,7 @@

from backoffice.mixins import OrgaTeamPermissionMixin
from camps.mixins import CampViewMixin
from feedback.models import Feedback
from feedback.models import CampFeedback
from profiles.models import Profile
from shop.models import OrderProductRelation
from shop.models import Product
Expand Down Expand Up @@ -315,33 +316,52 @@ def get_context_data(self, *args, **kwargs):
# FEEDBACK


class EventFeedbackListView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
class CampFeedbackListView(CampViewMixin, OrgaTeamPermissionMixin, ListView):
"""View for listing all feedbacks."""

model = Feedback
model = CampFeedback
template_name = "feedback_list.html"
context_object_name = "feedback"


class EventFeedbackDetailView(CampViewMixin, OrgaTeamPermissionMixin, DetailView):
class CampFeedbackDetailView(CampViewMixin, OrgaTeamPermissionMixin, DetailView):
"""View for listing all feedbacks."""

model = Feedback
model = CampFeedback
template_name = "feedback_detail.html"
context_object_name = "feedback"


class EventFeedbackProcessView(CampViewMixin, OrgaTeamPermissionMixin, UpdateView):
"""View for marking feedback as processed"""
class CampFeedbackProcessView(CampViewMixin, OrgaTeamPermissionMixin, UpdateView):
"""View for processing feedback."""

model = Feedback
fields = ["processed_at", "processed_by"]
template_name = "feedback_list_processed_confirm.html"
model = CampFeedback
fields = ["state"]
template_name = "feedback_processed_confirm.html"
context_object_name = "feedback"

def get_context_data(self, *args, **kwargs):
"""Get `state` from URL and if valid add to context else raise error."""
context = super().get_context_data(*args, **kwargs)
state_arg = self.kwargs.get("state")

try:
context.update({"state": self.object.StateChoices(state_arg)})
except ValueError:
raise BadRequest(f"Argument: '{state_arg}' is invalid.")

return context

def post(self, request, *args, **kwargs):
"""Mark feedback as processed."""
"""Process feedback with parsed state."""
self.object = self.get_object()
self.object.processed_by = self.request.user
self.object.processed_at = timezone.now()
self.object.save()
state = kwargs.get("state")

try:
self.object.process_feedback(state, request.user)
except ValueError:
raise BadRequest(f"Argument: '{state}' is invalid.")

return HttpResponseRedirect(
reverse("backoffice:feedback_list", kwargs={"camp_slug": self.camp.slug}),
)
Expand Down
4 changes: 2 additions & 2 deletions src/bornhack/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from camps.views import CampListView
from camps.views import CampRedirectView
from contact.views import ContactView
from feedback.views import FeedbackCreate
from feedback.views import CampFeedbackCreate
from info.views import CampInfoView
from maps.views import MapView
from maps.views import UserLocationApiView
Expand Down Expand Up @@ -221,7 +221,7 @@
path("teams/", include("teams.urls", namespace="teams")),
path("rideshare/", include("rideshare.urls", namespace="rideshare")),
path("backoffice/", include("backoffice.urls", namespace="backoffice")),
path("feedback/", FeedbackCreate.as_view(), name="feedback"),
path("feedback/", CampFeedbackCreate.as_view(), name="feedback"),
path("economy/", include("economy.urls", namespace="economy")),
path("wishlist/", include("wishlist.urls", namespace="wishlist")),
path("facilities/", include("facilities.urls", namespace="facilities")),
Expand Down
26 changes: 19 additions & 7 deletions src/feedback/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,32 @@
from django.contrib import admin
from django.utils import timezone

from .models import Feedback
from .models import CampFeedback


@admin.register(Feedback)
@admin.register(CampFeedback)
class FeedbackAdmin(admin.ModelAdmin):
list_display = ("user", "camp", "feedback", "processed_at", "processed_by")
list_display = ("user", "camp", "feedback", "state", "processed_at", "processed_by")
list_filter = ["processed_at"]
readonly_fields = ["user", "camp", "feedback", "processed_at", "processed_by"]
readonly_fields = ["user", "camp", "feedback", "state", "processed_at", "processed_by"]

actions = ["mark_as_processed"]
actions = [
"mark_as_reviewed",
"mark_as_spam"
]

@admin.action(description="Mark this feedback as processed")
def mark_as_processed(self, request, queryset) -> None:
@admin.action(description="Mark this feedback as reviewed")
def mark_as_reviewed(self, request, queryset) -> None:
queryset.update(
processed_at=timezone.now(),
processed_by=request.user,
state="reviewed",
)

@admin.action(description="Mark this feedback as spam")
def mark_as_spam(self, request, queryset) -> None:
queryset.update(
processed_at=timezone.now(),
processed_by=request.user,
state="spam",
)
Loading
Loading