US #954: Task #1115: Create feedback endpoint

remotes/origin/enhancement/email-actions
David Barragán Merino 2014-09-29 16:44:14 +02:00
parent b82f6a4743
commit bcb2948417
17 changed files with 397 additions and 0 deletions

View File

@ -193,6 +193,7 @@ INSTALLED_APPS = [
"taiga.timeline", "taiga.timeline",
"taiga.mdrender", "taiga.mdrender",
"taiga.export_import", "taiga.export_import",
"taiga.feedback",
"rest_framework", "rest_framework",
"djmail", "djmail",
@ -323,6 +324,11 @@ TAGS_PREDEFINED_COLORS = ["#fce94f", "#edd400", "#c4a000", "#8ae234",
"#5c3566", "#ef2929", "#cc0000", "#a40000", "#5c3566", "#ef2929", "#cc0000", "#a40000",
"#2e3436",] "#2e3436",]
# Feedback module settings
FEEDBACK_ENABLED = True
FEEDBACK_EMAIL = "support@taiga.io"
# NOTE: DON'T INSERT MORE SETTINGS AFTER THIS LINE # NOTE: DON'T INSERT MORE SETTINGS AFTER THIS LINE
TEST_RUNNER="django.test.runner.DiscoverRunner" TEST_RUNNER="django.test.runner.DiscoverRunner"

View File

@ -0,0 +1,17 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
default_app_config = "taiga.feedback.apps.FeedbackAppConfig"

51
taiga/feedback/api.py Normal file
View File

@ -0,0 +1,51 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base import response
from taiga.base.api import viewsets
from . import permissions
from . import serializers
from . import services
import copy
class FeedbackViewSet(viewsets.ViewSet):
permission_classes = (permissions.FeedbackPermission,)
serializer_class = serializers.FeedbackEntrySerializer
def create(self, request, **kwargs):
self.check_permissions(request, "create", None)
data = copy.deepcopy(request.DATA)
data.update({"full_name": request.user.get_full_name(),
"email": request.user.email})
serializer = self.serializer_class(data=data)
if not serializer.is_valid():
return response.BadRequest(serializer.errors)
self.object = serializer.save(force_insert=True)
extra = {
"HTTP_HOST": request.META.get("HTTP_HOST", None),
"HTTP_REFERER": request.META.get("HTTP_REFERER", None),
"HTTP_USER_AGENT": request.META.get("HTTP_USER_AGENT", None),
}
services.send_feedback(self.object, extra)
return response.Ok(serializer.data)

32
taiga/feedback/apps.py Normal file
View File

@ -0,0 +1,32 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.apps import AppConfig
from django.apps import apps
from django.conf import settings
from django.conf.urls import include, url
from .routers import router
class FeedbackAppConfig(AppConfig):
name = "taiga.feedback"
verbose_name = "Feedback"
def ready(self):
if settings.FEEDBACK_ENABLED:
from taiga.urls import urlpatterns
urlpatterns.append(url(r'^api/v1/', include(router.urls)))

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='FeedbackEntry',
fields=[
('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)),
('full_name', models.CharField(verbose_name='full name', max_length=256)),
('email', models.EmailField(verbose_name='email address', max_length=255)),
('comment', models.TextField(verbose_name='comment')),
('created_date', models.DateTimeField(auto_now_add=True, verbose_name='created date')),
],
options={
'verbose_name': 'feedback entry',
'verbose_name_plural': 'feedback entries',
'ordering': ['-created_date', 'id'],
},
bases=(models.Model,),
),
]

View File

34
taiga/feedback/models.py Normal file
View File

@ -0,0 +1,34 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db import models
from django.utils.translation import ugettext_lazy as _
class FeedbackEntry(models.Model):
full_name = models.CharField(null=False, blank=False, max_length=256,
verbose_name=_('full name'))
email = models.EmailField(null=False, blank=False, max_length=255,
verbose_name=_('email address'))
comment = models.TextField(null=False, blank=False,
verbose_name=_("comment"))
created_date = models.DateTimeField(null=False, blank=False, auto_now_add=True,
verbose_name=_("created date"))
class Meta:
verbose_name = "feedback entry"
verbose_name_plural = "feedback entries"
ordering = ["-created_date", "id"]

View File

@ -0,0 +1,23 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api.permissions import TaigaResourcePermission
from taiga.base.api.permissions import IsAuthenticated
class FeedbackPermission(TaigaResourcePermission):
create_perms = IsAuthenticated()

22
taiga/feedback/routers.py Normal file
View File

@ -0,0 +1,22 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base import routers
from . import api
router = routers.DefaultRouter(trailing_slash=False)
router.register(r"feedback", api.FeedbackViewSet, base_name="feedback")

View File

@ -0,0 +1,24 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from rest_framework import serializers
from . import models
class FeedbackEntrySerializer(serializers.ModelSerializer):
class Meta:
model = models.FeedbackEntry

View File

@ -0,0 +1,29 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf import settings
from djmail.template_mail import MagicMailBuilder
def send_feedback(feedback_entry, extra):
support_email = settings.FEEDBACK_EMAIL
if support_email:
mbuilder = MagicMailBuilder()
email = mbuilder.feedback_notification(support_email, {"feedback_entry": feedback_entry,
"extra": extra})
email.send()

View File

@ -0,0 +1,37 @@
{% extends "emails/base.jinja" %}
{% block body %}
<table border="0" width="100%" cellpadding="4" cellspacing="10" class="table-body" style="border-collapse: collapse;">
<tr>
<td valign="top" style="border-top: 1px solid gray; border-bottom: 1px solid gray;">
<strong>From:</strong>
</td>
<td style="border-top: 1px solid gray; border-bottom: 1px solid gray;">
{{ feedback_entry.full_name }} [{{ feedback_entry.email }}]
</td>
</tr>
<tr>
<td valign="top" style="border-top: 1px solid gray; border-bottom: 1px solid gray;">
<strong>Comment:</strong>
</td>
<td style="border-top: 1px solid gray; border-bottom: 1px solid gray;">
{{ feedback_entry.comment|linebreaks }}
</td>
</tr>
{% if extra %}
<tr>
<td valign="top" style="border-top: 1px solid gray; border-bottom: 1px solid gray;">
<strong>Extra:</strong>
</td>
<td style="border-top: 1px solid gray; border-bottom: 1px solid gray;">
<dl>
{% for k, v in extra.items() %}
<dt>{{ k }}</dt>
<dd>{{ v }}</dd>
{% endfor %}
</dl>
</td>
</tr
{% endif %}>
</table>
{% endblock %}

View File

@ -0,0 +1,11 @@
---------
- From: {{ feedback_entry.full_name }} [{{ feedback_entry.email }}]
---------
- Comment:
{{ feedback_entry.comment }}
---------{% if extra %}
- Extra:
{% for k, v in extra.items() %}
- {{ k }}: {{ v }}
{% endfor %}
{% endif %}----------

View File

@ -0,0 +1 @@
[Taiga] Feedback from {{ feedback_entry.full_name }} <{{ feedback_entry.email }}>

View File

@ -128,4 +128,11 @@ router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links")
# Notify policies # Notify policies
from taiga.projects.notifications.api import NotifyPolicyViewSet from taiga.projects.notifications.api import NotifyPolicyViewSet
router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notifications") router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notifications")
# feedback
# - see taiga.feedback.routers and taiga.feedback.apps

View File

@ -0,0 +1,27 @@
from django.core.urlresolvers import reverse
from tests import factories as f
from tests.utils import helper_test_http_method
from taiga.base.utils import json
import pytest
pytestmark = pytest.mark.django_db
@pytest.fixture
def data():
m = type("Models", (object,), {})
m.user = f.UserFactory.create()
return m
def test_feedback_create(client, data):
url = reverse("feedback-list")
users = [None, data.user]
feedback_data = {"comment": "One feedback comment"}
feedback_data = json.dumps(feedback_data)
results = helper_test_http_method(client, 'post', url, feedback_data, users)
assert results == [401, 200]

View File

@ -0,0 +1,47 @@
from django.core.urlresolvers import reverse
from tests import factories as f
from tests.utils import helper_test_http_method
from taiga.base.utils import json
import pytest
pytestmark = pytest.mark.django_db
@pytest.fixture
def user():
return f.UserFactory.create()
def test_create_feedback(client, user):
url = reverse("feedback-list")
feedback_data = {"comment": "One feedback comment"}
feedback_data = json.dumps(feedback_data)
client.login(user)
response = client.post(url, feedback_data, content_type="application/json")
assert response.status_code == 200
assert response.data.get("id", None)
assert response.data.get("created_date", None)
assert response.data.get("full_name", user.full_name)
assert response.data.get("email", user.email)
client.logout()
def test_create_feedback_without_comments(client, user):
url = reverse("feedback-list")
feedback_data = json.dumps({})
client.login(user)
response = client.post(url, feedback_data, content_type="application/json")
assert response.status_code == 400
assert response.data.get("comment", None)
client.logout()