Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feedback form #65

Merged
merged 35 commits into from
Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
41ec808
add exchangelib to requirements
nesnoj Jun 28, 2019
c25c12c
add feedback form view (+views for error and success)
nesnoj Jun 28, 2019
b907429
add templates for feedback form, success and error page
nesnoj Jun 28, 2019
97751cf
define django form for feedback
nesnoj Jun 28, 2019
2a4b472
pimp docstring
nesnoj Jun 28, 2019
05b4278
add feedback views to urls
nesnoj Jun 28, 2019
994bb96
get exchange mail cfg from env vars
nesnoj Jun 28, 2019
70a2ed6
add fct to send mail via exchange server
nesnoj Jun 28, 2019
4da01ee
move message preparation to view
nesnoj Jun 28, 2019
50db0e0
update CHANGELOG
nesnoj Jun 28, 2019
5c7e650
add feedback form to helpers doc
nesnoj Jun 29, 2019
41efb17
add email note in app.cfg to helpers doc
nesnoj Jun 29, 2019
af42c67
check for env vars' existence, redirect to error page if one or more …
nesnoj Jun 29, 2019
f4f7512
add different error types to feedback error view with corresponding m…
nesnoj Jun 29, 2019
dd0405b
include error type in urls
nesnoj Jun 29, 2019
cc30a41
use error text from context in feedback error template
nesnoj Jun 29, 2019
8a073d2
fix typo
nesnoj Jun 29, 2019
8e8cb66
pep8
nesnoj Jun 29, 2019
a59dee5
rm orphaned arg
nesnoj Jun 29, 2019
98c638d
fix import order
nesnoj Jun 29, 2019
762758a
fix travis: rm unnecessary else
nesnoj Jun 29, 2019
3340b5b
fix travis: rm unnecessary __init__
nesnoj Jun 29, 2019
5d2e6c8
add docstring to fct send_mail()
nesnoj Jun 29, 2019
38b135e
fix travis: rm args and kwargs
nesnoj Jun 30, 2019
a511b86
fix travis: disable too-many-ancestors err in FeedbackView
nesnoj Jun 30, 2019
679e9fd
fix travis: add ignore on import-error and broad-except
nesnoj Jul 2, 2019
62ca879
fix pep8
nesnoj Jul 2, 2019
03bedf7
change icon in feedback error page
nesnoj Jul 2, 2019
779c4d0
fix name of WAM index page
nesnoj Jul 2, 2019
44c7896
rm feedback form from wam's URLs as it is to be used in apps only
nesnoj Jul 3, 2019
cc9c2ea
rm orphaned import
nesnoj Jul 3, 2019
f3ad5a4
use config file instead of env vars for exchange credentials
nesnoj Jul 3, 2019
ff7b906
add WAM exchange params to example config
nesnoj Jul 3, 2019
7b01c25
amend helpers doc
nesnoj Jul 3, 2019
a261608
Merge branch 'dev' into features/feedback_form
nesnoj Jul 3, 2019
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ Here is a template for new release sections
- continuous integration with TravisCI (`.travis.yml`)
- linting tests and their config files (`.pylintrc` and `.flake8`)
- tests/ folder
- support custom id in InfoButton widget
- support custom id in InfoButton widget
- add feedback form

### Changed
- fix flake8 and pylint errors
Expand Down
31 changes: 31 additions & 0 deletions doc/helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,37 @@ Additionally, the label template tag supports two attributes:
If no label is found or given (sub-) section is not found, *None* will be returned.


Feedback Form
-------------

A feedback form is available which can be used in all apps. The feedback is send via e-mail using an Exchange account.
Required environment variables for the Exchange account are WAM_EXCHANGE_ACCOUNT, WAM_EXCHANGE_EMAIL and WAM_EXCHANGE_PW.

To use the form, just add the view to your urls like

.. code:: python

# my_app/urls.py

from utils.views import FeedbackView

admin_url_patterns = [
path(<path to other view>),
...,
path('feedback/', FeedbackView.as_view(app_name='<my app name>'), name='feedback')
]

Make sure you have the parameter ``email`` set in your *app.cfg*, example:

.. code:: text

# my_app/app.cfg
category = app
name = ...
icon = ...
email = 'address_of_app_admin@domain.tld'


.. _custom_admin_site:

Customizing Admin Site
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pandas
whitenoise
celery
configobj
exchangelib
django-markdownx
django-crispy-forms
psycopg2-binary
Expand Down
60 changes: 60 additions & 0 deletions templates/feedback.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{% extends 'base.html' %}

{% load static %}
{% block content %}
<div class="u-vh--100">
<main>
<section class="grid-x align-center l-bg-color--light hp-features">
<div class="cell medium-8 large-6">
<h1><i class='icon ion-chatbubbles icon--large'></i> Ihr Feedback</h1>
<p><strong>App: <a href="../" class="u-padding-top--l u-padding-bt--l">{{ app_name }}</a></strong></p>
{% if intro_text %}
<p>{{ intro_text }}</p>
{% else %}
<p>Hier können Sie uns eine Rückmeldung zur App geben, wir freuen uns über Ihre Nachricht!</p>
{% endif %}

<form method="post">
{% csrf_token %}
{{ form }}
<button class="btn btn-cta" type="submit" style="float: right;">Abschicken</button>
</form>
</div>
</section>
</main>

<footer class="footer-hp l-bg-color--w">
<div class="grid-x">

<div class="cell footer-hp__logo">
<ul>
<li>
<a href="{% url 'index' %}" rel=”noopener” title="WAM-Startseite">
<img class="footer-hp__logo-wam" src="{% static '/img/logos/WAM_logo.png' %}" alt="Logo WAM">
</a>
</li>
<li>
<a href="https://reiner-lemoine-institut.de/" target="_blank" rel=”noopener” title="Reiner Lemoine Institut">
<img class="footer-hp__logo-rli" src="{% static 'img/rli_logo.png' %}" alt="Logo Reiner Lemoine Institut">
</a>
</li>
</ul>
</div>

<div class="cell medium-10 medium-offset-1">
<ul class="u-text--center" id="footer-links">
<li><a class="anchor-text--dark" href="{% url 'contact' %}">Kontakt</a></li>
<li><a class="anchor-text--dark" href="{% url 'impressum' %}">Impressum</a></li>
<li><a class="anchor-text--dark" href="{% url 'privacy' %}">Datenschutz</a></li>
<li class="hide-for-small-only">
<p class="u-no-margin">&copy; Reiner Lemoine Institut gGmbH</p>
</li>
</ul>
</div>

</div>
</footer>

</div>

{% endblock %}
10 changes: 10 additions & 0 deletions templates/feedback_error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends 'base.html' %}

{% block content %}
<div class="grid-x grid-padding-x align-center">
<div class="cell medium-8 large-6">
<h1><i class='icon ion-alert-circled icon--large'></i> {{ error_text }}</h1>
<p><a href="{% url 'index' %}" class="u-padding-top--l u-padding-bt--l">Zur WAM-Startseite</a></p>
</div>
</div>
{% endblock %}
10 changes: 10 additions & 0 deletions templates/feedback_successful.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends 'base.html' %}

{% block content %}
<div class="grid-x grid-padding-x align-center">
<div class="cell medium-8 large-6">
<h1><i class='icon ion-chatbubbles icon--large'></i> Vielen Dank für Ihr Feedback!</h1>
<p><a href="{% url 'index' %}" class="u-padding-top--l u-padding-bt--l">Zur WAM-Startseite</a></p>
</div>
</div>
{% endblock %}
19 changes: 19 additions & 0 deletions utils/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django import forms


class FeedbackForm(forms.Form):
"""Input form for feedback page"""
from_name = forms.CharField(required=False,
max_length=100,
label='Ihr Name (optional)')
from_email = forms.EmailField(required=False,
label='Ihre E-Mail-Adresse (optional)')
subject = forms.CharField(required=True,
max_length=100,
label='Betreff')
message = forms.CharField(widget=forms.Textarea,
required=True,
label='Ihr Feedback')

def submit(self):
pass
61 changes: 61 additions & 0 deletions utils/mail.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import logging
from requests.exceptions import ConnectionError # pylint: disable=import-error

from exchangelib import Credentials, Account, Message, Mailbox # pylint: disable=import-error
from exchangelib.errors import AutoDiscoverFailed # pylint: disable=import-error
from wam.settings import WAM_EXCHANGE_ACCOUNT, WAM_EXCHANGE_EMAIL, WAM_EXCHANGE_PW


def send_email(to_email, subject, message):
"""Send E-mail via MS Exchange Server using credentials from env vars

Parameters
----------
to_email : :obj:`str`
Target mail address
subject : :obj:`str`
Subject of mail
message : :obj:`str`
Message body of mail

Returns
-------
:obj:`bool`
Success status (True: successful)
"""

credentials = Credentials(WAM_EXCHANGE_ACCOUNT,
WAM_EXCHANGE_PW)
try:
account = Account(WAM_EXCHANGE_EMAIL,
credentials=credentials,
autodiscover=True)
except ConnectionError:
err_msg = 'Feedback-Form - Verbindungsfehler!'
logging.error(err_msg)
return False
except AutoDiscoverFailed:
err_msg = 'Feedback-Form - Konto- oder Authentifizierungsfehler!'
logging.error(err_msg)
return False
except Exception as err: # pylint: disable=broad-except
err_msg = f'Feedback-Form - Sonstiger Fehler: {err}'
logging.error(err_msg)
return False

recipients = [Mailbox(email_address=to_email)]

m = Message(account=account,
folder=account.sent,
subject=subject,
body=message,
to_recipients=recipients)

try:
m.send_and_save()
except Exception as err: # pylint: disable=broad-except
err_msg = f'Feedback-Form - Fehler beim Mailversand: {err}'
logging.error(err_msg)
return False

return True
129 changes: 129 additions & 0 deletions utils/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import os
import logging
from configobj import ConfigObj

from django.views.generic.edit import FormView
from django.views.generic import TemplateView
from django.core.validators import validate_email, ValidationError
from django.shortcuts import redirect

from wam.settings import BASE_DIR, WAM_EXCHANGE_ACCOUNT, WAM_EXCHANGE_EMAIL, WAM_EXCHANGE_PW
from utils.forms import FeedbackForm
from utils.mail import send_email


class FeedbackView(FormView): # pylint: disable=too-many-ancestors
"""Feedback form which sends an E-mail to app admin"""

app_name = None
intro_text = None
template_name = 'feedback.html'
form_class = FeedbackForm
success_url = '/feedback_thanks/'
error_url = '/feedback_error/'

def __init__(self,
app_name=None,
intro_text=None):
"""
Parameters
----------
app_name : :obj:`str`
Name of app the form should be created for
intro_text : :obj:`str`
Optional. Custom introductory text (inserted before form),
defaults to standard welcome text (see template).
"""
super(FeedbackView, self).__init__()

if app_name is not None:
self.app_name = app_name

# read and validate app admin's mail address from app.cfg
app_config = ConfigObj(os.path.join(BASE_DIR, app_name, 'app.cfg'))
email = app_config.get('email', None)
if email is not None:
try:
validate_email(email)
self.to_email = email
except ValidationError:
raise ValidationError(
f'E-mail address in {app_name}`s app.cfg is invalid!')
else:
raise ValueError('Parameter "app_name" must be specified!')

self.intro_text = intro_text

def form_valid(self, form):
form.submit()

subject, body = self.prepare_message(**form.cleaned_data)
success = send_email(to_email=self.to_email,
subject=subject,
message=body)
if success:
return super().form_valid(form)

return redirect('feedback_error', err_type='send')

def get_context_data(self, **kwargs):
context = super(FeedbackView, self).get_context_data(**kwargs)

context['app_name'] = self.app_name
context['intro_text'] = self.intro_text

return context

def get(self, request, *args, **kwargs):
# check if env vars are set, if not redirect to error page
if WAM_EXCHANGE_ACCOUNT is None or \
WAM_EXCHANGE_EMAIL is None or \
WAM_EXCHANGE_PW is None:
err_msg = 'Feedback-Form - Konfigurationsfehler: ' \
'Umgebungsvariablen nicht gesetzt oder unvollständig!'
logging.error(err_msg)
return redirect('feedback_error', err_type='config')

return super().get(request, *args, **kwargs)

def prepare_message(self, **kwargs):
subject = f'Nachricht über WAM Feedback-Formular: ' \
f'App {self.app_name}'
body = f'Sie haben eine Nachricht über das Feedback-Formular der WAM ' \
f'erhalten.\n\n' \
f'App: {self.app_name}\n' \
f'Absender: {kwargs.get("from_name", "")} ' \
f'({kwargs.get("from_email", "")})\n' \
f'Betreff: {kwargs.get("subject", "")}\n' \
f'========================================\n' \
f'{kwargs.get("message", "")}\n' \
f'========================================\n' \
f'Bitte antworte nicht auf diese E-Mail, junger PadaWAM!\n' \
f'Gez. Obi WAM Kenobi'
return subject, body


class FeedbackSuccessful(TemplateView):
template_name = 'feedback_successful.html'


class FeedbackError(TemplateView):
"""Error page for feedback form"""

template_name = 'feedback_error.html'

def get(self, request, *args, **kwargs):
context = self.get_context_data()

# get error type and include corresponding message in context
err_type = kwargs.get('err_type')
if err_type == 'config':
error_text = 'Das Feedback-Formular ist nicht richtig konfiguriert.'
elif err_type == 'send':
error_text = 'Beim Senden ist leider ein Fehler aufgetreten.'
else:
error_text = 'Das Feedback-Formular funktioniert nicht richtig.'

context['error_text'] = error_text

return self.render_to_response(context)
5 changes: 5 additions & 0 deletions wam/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,8 @@
importlib.import_module(f'{app}.app_settings', package='wam')
except ModuleNotFoundError:
pass

# E-mail config for outgoing mails (used by exchangelib)
WAM_EXCHANGE_ACCOUNT = os.environ.get('WAM_EXCHANGE_ACCOUNT')
WAM_EXCHANGE_EMAIL = os.environ.get('WAM_EXCHANGE_EMAIL')
WAM_EXCHANGE_PW = os.environ.get('WAM_EXCHANGE_PW')
6 changes: 5 additions & 1 deletion wam/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
from meta import models
from meta.views import AppListView, AssumptionsView

from utils.views import FeedbackSuccessful, FeedbackError


urlpatterns = [
path('', views.IndexView.as_view()),
path('', views.IndexView.as_view(), name='index'),
path('contact/', views.ContactView.as_view(), name='contact'),
path('privacy/', views.PrivacyView.as_view(), name='privacy'),
path('impressum/', views.ImpressumView.as_view(), name='impressum'),
Expand All @@ -36,6 +38,8 @@
path('accounts/login/', LoginView.as_view(template_name='login.html')),
path('access_denied/', TemplateView.as_view(
template_name='access_denied.html'), name='access_denied'),
path('feedback_thanks/', FeedbackSuccessful.as_view(), name='feedback_thanks'),
path('feedback_error/<err_type>/', FeedbackError.as_view(), name='feedback_error')
]

try:
Expand Down