Skip to content

Commit

Permalink
Add feedback form
Browse files Browse the repository at this point in the history
  • Loading branch information
blopker committed Nov 3, 2023
1 parent 3a06047 commit 1f13801
Show file tree
Hide file tree
Showing 16 changed files with 196 additions and 10 deletions.
7 changes: 5 additions & 2 deletions assets/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
}
input[type="email"],
input[type="password"],
input[type="text"], textarea, select {
/* @apply my-2 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-tyellow sm:text-sm sm:leading-6 !important; */
input[type="text"], select {
@apply input input-bordered;
}
textarea {
@apply textarea textarea-bordered;
}
label {
@apply text-sm font-medium leading-6 text-gray-900 !important;
}
Expand All @@ -46,6 +48,7 @@
@apply w-full block;
}


.badge {
@apply bg-purple-100 text-purple-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded-full dark:bg-purple-900 dark:text-purple-300;
}
Expand Down
10 changes: 10 additions & 0 deletions assets/js/components/navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ function LoggedInMenu({ user, links }) {
</a>
</li>
))}
<li>
<a class="pr-5 hover:text-tblue" href={links.feedback}>
Feedback
</a>
</li>
<li>
<a class="pr-5 text-tpinkTint hover:text-error" href={links.logout}>
Logout
Expand All @@ -137,6 +142,11 @@ function LoggedOutMenu({ links }) {
</a>
</li>
))}
<li>
<a class="pr-5 hover:text-tblue" href={links.feedback}>
Feedback
</a>
</li>
</ul>
</>
)
Expand Down
1 change: 1 addition & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ def b64_json_env(key: str):
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"totem.utils.middleware.robotnoindex",
"django_htmx.middleware.HtmxMiddleware",
"totem.utils.middleware.TimezoneMiddleware",
"totem.utils.middleware.CDNGuard",
]
Expand Down
2 changes: 1 addition & 1 deletion totem/static/css/styles.css

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions totem/static/js/app.min.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions totem/static/js/app.min.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions totem/templates/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
</p>
<a class="btn btn-primary items-center inline-flex mt-5"
href="https://secure.totem.org/b/3cs7vN8LDfqb8r6dQV">Donate</a>
<a class="btn btn-outline items-center inline-flex mt-5"
href="https://secure.totem.org/b/3cs7vN8LDfqb8r6dQV">Feedback</a>
</div>
<div class="flex-grow flex flex-wrap md:pl-20 -mb-10 md:mt-0 mt-5 md:text-left text-center">
<div class="flex-grow"></div>
Expand Down
1 change: 1 addition & 0 deletions totem/templates/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"logout": "{% url "account_logout" %}",
"login": "{% url "users:login" %}",
"profile": "{% url "users:profile" %}",
"feedback": "{% url "users:feedback" %}",
"marketing": [
{
"title": "How it works", "href": "{% url "pages:how-it-works" %}"
Expand Down
7 changes: 6 additions & 1 deletion totem/users/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from totem.users.forms import UserAdminChangeForm, UserAdminCreationForm

from .models import KeeperProfile, User
from .models import Feedback, KeeperProfile, User


@admin.register(User)
Expand Down Expand Up @@ -47,3 +47,8 @@ class UserAdmin(UserAdminImpersonateMixin, auth_admin.UserAdmin):
@admin.register(KeeperProfile)
class KeeperProfileAdmin(admin.ModelAdmin):
autocomplete_fields = ("user",)


@admin.register(Feedback)
class FeedbackAdmin(admin.ModelAdmin):
pass
41 changes: 41 additions & 0 deletions totem/users/migrations/0023_feedback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by Django 4.2.6 on 2023-11-02 21:52

from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import totem.utils.fields


class Migration(migrations.Migration):
dependencies = [
("users", "0022_keeperprofile"),
]

operations = [
migrations.CreateModel(
name="Feedback",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("email", models.EmailField(blank=True, max_length=254, null=True, verbose_name="Email Address")),
(
"message",
totem.utils.fields.MaxLengthTextField(
max_length=10000,
validators=[django.core.validators.MaxLengthValidator(10000)],
verbose_name="Feedback",
),
),
("date_created", models.DateTimeField(auto_now_add=True)),
(
"user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="feedback",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
11 changes: 11 additions & 0 deletions totem/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from totem.email.utils import validate_email_blocked
from totem.users.managers import UserManager
from totem.utils.fields import MaxLengthTextField
from totem.utils.hash import basic_hash
from totem.utils.md import MarkdownField, MarkdownMixin
from totem.utils.models import AdminURLMixin, SluggedModel
Expand Down Expand Up @@ -144,3 +145,13 @@ def __str__(self):

def get_absolute_url(self):
return self.user.get_absolute_url()


class Feedback(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="feedback", null=True)
email = EmailField(_("Email Address"), null=True, blank=True)
message = MaxLengthTextField(null=False, max_length=10000, blank=False, verbose_name=_("Feedback"))
date_created = models.DateTimeField(auto_now_add=True)

def __str__(self):
return f"<Feedback: {self.date_created}>"
26 changes: 26 additions & 0 deletions totem/users/templates/users/feedback.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block content %}
<h1 class="h1 text-center text-4xl font-bold pt-20">Feedback</h1>
<div class="max-w-[600px] m-auto pt-10 pb-20 px-5">
<p class="pb-5">
We love hearing about how we can improve Totem. If you have any feedback, please let us know!
</p>
<form action="{% url "users:feedback" %}" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
{% if not request.user.is_authenticated %}
<div>
{{ form.email.errors }}
<label for="{{ form.email.id_for_label }}">Email:</label>
{{ form.email }}
</div>
{% endif %}
<div class="pt-2">
{{ form.message.errors }}
<label for="{{ form.message.id_for_label }}">Your feedback:</label>
{{ form.message }}
</div>
<input class="btn btn-primary mt-5 w-full" type="submit" value="Submit">
</form>
</div>
{% endblock content %}
53 changes: 52 additions & 1 deletion totem/users/tests/test_forms.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
"""
Module for all Form Tests.
"""
from django.contrib.messages import get_messages
from django.test import TestCase
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from totem.users.forms import UserAdminCreationForm
from totem.users.models import User
from totem.users.models import Feedback, User
from totem.users.tests.factories import UserFactory
from totem.users.views import FeedbackForm


class TestUserAdminCreationForm:
Expand Down Expand Up @@ -34,3 +39,49 @@ def test_username_validation_error_msg(self, user: User):
assert len(form.errors) == 1
assert "email" in form.errors
assert form.errors["email"][0] == _("This email has already been taken.")


class TestUserFeedbackView(TestCase):
def test_feedback_form_display(self):
response = self.client.get(reverse("users:feedback"))
self.assertEqual(response.status_code, 200)
self.assertIsInstance(response.context["form"], FeedbackForm)

def test_feedback_form_submission_authenticated(self):
user = UserFactory()
self.client.force_login(user)
response = self.client.post(
reverse("users:feedback"),
data={
"message": "value2",
},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(Feedback.objects.count(), 1)
feedback = Feedback.objects.first()
assert feedback
self.assertEqual(feedback.user, user)
self.assertEqual(feedback.email, None)
self.assertEqual(feedback.message, "value2")
messages = list(get_messages(response.wsgi_request))
self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), "Feedback successfully submitted. Thank you!")

def test_feedback_form_submission_unauthenticated(self):
response = self.client.post(
reverse("users:feedback"),
data={
"email": "[email protected]",
"message": "value3",
},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(Feedback.objects.count(), 1)
feedback = Feedback.objects.first()
assert feedback
self.assertIsNone(feedback.user)
self.assertEqual(feedback.email, "[email protected]")
self.assertEqual(feedback.message, "value3")
messages = list(get_messages(response.wsgi_request))
self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), "Feedback successfully submitted. Thank you!")
2 changes: 2 additions & 0 deletions totem/users/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
LogInView,
user_dashboard_view,
user_detail_view,
user_feedback_view,
user_index_view,
user_profile_delete_view,
user_profile_image_view,
Expand All @@ -19,6 +20,7 @@
path("profile/", user_profile_view, name="profile"),
path("profile/delete", user_profile_delete_view, name="profile-delete"),
path("profile/image", user_profile_image_view, name="profile-image"),
path("feedback/", user_feedback_view, name="feedback"),
path("", user_index_view, name="index"),
path("u/<str:slug>/", view=user_detail_view, name="detail"),
]
26 changes: 25 additions & 1 deletion totem/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from . import analytics
from .forms import LoginForm
from .models import User
from .models import Feedback, User


def user_detail_view(request, slug):
Expand Down Expand Up @@ -202,3 +202,27 @@ def user_profile_delete_view(request):
messages.success(request, "Account successfully deleted.")
return redirect("pages:home")
return HttpResponseForbidden()


class FeedbackForm(forms.ModelForm):
class Meta:
model = Feedback
fields = ("email", "message")
widgets = {
"message": forms.Textarea(attrs={"rows": 5, "cols": 15}),
}


def user_feedback_view(request):
form = FeedbackForm()
if request.method == "POST":
data = request.POST.copy()
form = FeedbackForm(data)
if form.is_valid():
if request.user.is_authenticated:
Feedback.objects.create(**form.cleaned_data, user=request.user)
else:
Feedback.objects.create(**form.cleaned_data)
messages.success(request, "Feedback successfully submitted. Thank you!")
form = FeedbackForm()
return render(request, "users/feedback.html", context={"form": form})
9 changes: 9 additions & 0 deletions totem/utils/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.core.validators import MaxLengthValidator
from django.db.models import TextField


class MaxLengthTextField(TextField):
def __init__(self, *args, **kwargs):
kwargs["max_length"] = kwargs.get("max_length", 10000)
kwargs["validators"] = kwargs.get("validators", [MaxLengthValidator(kwargs["max_length"])])
super().__init__(*args, **kwargs)

0 comments on commit 1f13801

Please sign in to comment.