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

Feedback feature with screenshot and browser information #953

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Empty file added feedback/__init__.py
Empty file.
50 changes: 50 additions & 0 deletions feedback/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import json

from django.contrib import admin
from .models import Feedback
from django.utils.translation import ugettext_lazy as _, pgettext_lazy


# Display an html table from a dict
# Credit: original author Rogerio Hilbert
def pretty_items(r, d, nametag="<strong>%s: </strong>", itemtag='<li>%s</li>\n',
valuetag="%s", blocktag=('<ul>', '</ul>\n')):
if isinstance(d, dict):
r.append(blocktag[0])
for k, v in d.items():
name = nametag % k
if isinstance(v, dict) or isinstance(v, list):
r.append(itemtag % name)
pretty_items(r, v, nametag, itemtag, valuetag, blocktag)
else:
value = valuetag % v
r.append(itemtag % (name + value))
r.append(blocktag[1])
elif isinstance(d, list):
r.append(blocktag[0])
for i in d:
if isinstance(i, dict) or isinstance(i, list):
r.append(itemtag % " - ")
pretty_items(r, i, nametag, itemtag, valuetag, blocktag)
else:
r.append(itemtag % i)
r.append(blocktag[1])


class FeedbackAdmin(admin.ModelAdmin):
list_display = ("comment", "url", "screenshot_thumb", "user", "email", "created")
list_filter = ("created", "user", "url")
search_fields = ("comment", "user__email", "user__name")
readonly_fields = ("comment", "url", "user", "browser_html", "screenshot_thumb")
exclude = ('browser', 'screenshot')
ordering = ("-created",)

def browser_html(self, feedback):
if feedback.browser:
r = []
pretty_items(r, json.loads(feedback.browser))
return u''.join(r)
browser_html.allow_tags = True
browser_html.short_description = pgettext_lazy("Admin model", "Browser Info")

admin.site.register(Feedback, FeedbackAdmin)
5 changes: 5 additions & 0 deletions feedback/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class FeedbackConfig(AppConfig):
name = 'feedback'
9 changes: 9 additions & 0 deletions feedback/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.forms import ModelForm
from .models import Feedback


class FeedbackForm(ModelForm):

class Meta:
model = Feedback
fields = "__all__"
30 changes: 30 additions & 0 deletions feedback/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 2.1 on 2018-08-25 05:44

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Feedback',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.CharField(max_length=255, verbose_name='Url')),
('browser', models.TextField(verbose_name='Browser')),
('comment', models.TextField(verbose_name='Comment')),
('screenshot', models.ImageField(blank=True, null=True, upload_to='static/screenshots/', verbose_name='Screenshot')),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Creation date')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
),
]
20 changes: 20 additions & 0 deletions feedback/migrations/0002_feedback_phone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 2.1 on 2018-08-25 17:23

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('feedback', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='feedback',
name='phone',
field=models.CharField(default=1234567890, max_length=14, validators=[django.core.validators.RegexValidator(code='invalid_mobile', message='Please Enter 10/11 digit mobile number or landline as 0<std code><phone number>', regex='^((\\+91|91|0)[\\- ]{0,1})?[456789]\\d{9}$')], verbose_name='Phone - ഫോണ്\u200d നമ്പര്\u200d'),
preserve_default=False,
),
]
Empty file added feedback/migrations/__init__.py
Empty file.
28 changes: 28 additions & 0 deletions feedback/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.conf import settings
from django.db import models
from django.core.validators import RegexValidator
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _


# Create your models here.
class Feedback(models.Model):
url = models.CharField(_('Url'), max_length=255)
browser = models.TextField(_('Browser'))
comment = models.TextField(_('Comment'))
screenshot = models.ImageField(_('Screenshot'), blank=True, null=True, upload_to="static/screenshots/")

user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.SET_NULL, blank=True, null=True)
email = models.EmailField(max_length=254, blank=True, null=True)
phone_number_regex = RegexValidator(regex='^((\+91|91|0)[\- ]{0,1})?[456789]\d{9}$', message='Please Enter 10/11 digit mobile number or landline as 0<std code><phone number>', code='invalid_mobile')
phone = models.CharField(max_length=14,verbose_name='Phone - ഫോണ്‍ നമ്പര്‍', validators=[phone_number_regex])
created = models.DateTimeField(_('Creation date'), auto_now_add=True)


def screenshot_thumb(self):
if self.screenshot:
return mark_safe(u'<img src="%s" width="%s" />' % (self.screenshot.url, "50%"))
else:
return 'No Image Found'
screenshot_thumb.allow_tags = True
screenshot_thumb.short_description = _("Screenshot")
2 changes: 2 additions & 0 deletions feedback/templates/initButtonText.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{% load i18n %}
{% trans 'Send feedback' %}
42 changes: 42 additions & 0 deletions feedback/templates/js_inc.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{% load static %}

{% block ajax_csrf %}
<script type="text/javascript">
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
</script>
{% endblock %}

{% block enable_feedback %}
<script src="{% static 'feedback/feedback.min.js' %}"></script>
<script type="text/javascript">
$(function () {
$.feedback({
ajaxURL: "{% url 'feedback:post_feedback' %}",
html2canvasURL: "{% static 'feedback/html2canvas.min.js' %}",
feedbackButton: "#feedback-btn",
initButtonText: "{% filter escapejs %}{% include 'initButtonText.txt' %}{% endfilter %}",
postHTML: false,
tpl: {
description: "{% filter escapejs %}{% include 'tpl-description.html' %}{% endfilter %}",
highlighter: "{% filter escapejs %}{% include 'tpl-highlighter.html' %}{% endfilter %}",
overview: "{% filter escapejs %}{% include 'tpl-overview.html' %}{% endfilter %}",
submitSuccess:"{% filter escapejs %}{% include 'tpl-submit-success.html' %}{% endfilter %}",
submitError: "{% filter escapejs %}{% include 'tpl-submit-error.html' %}{% endfilter %}"
},
initialBox: true
});
});
</script>
{% endblock %}
13 changes: 13 additions & 0 deletions feedback/templates/tpl-description.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% load i18n %}
<div id="feedback-welcome">{% csrf_token %}
<div class="feedback-logo">{% trans 'Feedback' %}</div>
<p>{% trans 'Feedback lets you send us suggestions about this site. We welcome problem reports, feature ideas and general comments.' %}</p>
<p>{% trans 'If you wish to be contacted please leave us your phone:' %}</p>
<input type='text' name="Phone" id="feedback-user-phone">
<p>{% trans 'Start by writing a brief description:' %}</p>
<textarea id="feedback-note-tmp"></textarea>
<p>{% trans "Next we'll let you identify areas of the page related to your description." %}</p>
<button id="feedback-welcome-next" class="feedback-next-btn feedback-btn-gray">{% trans 'Next' %}</button>
<div id="feedback-welcome-error">{% trans 'Please enter a description.' %}</div>
<div class="feedback-wizard-close"></div>
</div>
22 changes: 22 additions & 0 deletions feedback/templates/tpl-highlighter.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% load i18n %}
<div id="feedback-highlighter">{% csrf_token %}
<div class="feedback-logo">{% trans 'Feedback' %}</div>
<p>{% trans "Click and drag on the page to help us better understand your feedback. You can move this dialog if it's in the way." %}</p>
<button class="feedback-sethighlight feedback-active">
<span class="ico"></span>
<span>{% trans 'Highlight' %}</span></button>
<label>{% trans 'Highlight areas relevant to your feedback.' %}</label>
<button class="feedback-setblackout">
<span class="ico"></span>
<span>{% trans 'Black out' %}</span></button>
<label class="lower">{% trans 'Black out any personal information.' %}</label>
<div class="feedback-buttons">
<button id="feedback-highlighter-next" class="feedback-next-btn feedback-btn-gray">
{% trans 'Next' %}
</button>
<button id="feedback-highlighter-back" class="feedback-back-btn feedback-btn-gray">
{% trans 'Back' %}
</button>
</div>
<div class="feedback-wizard-close"></div>
</div>
20 changes: 20 additions & 0 deletions feedback/templates/tpl-overview.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{% load i18n %}
<div id="feedback-overview">{% csrf_token %}
<div class="feedback-logo">{% trans 'Feedback' %}</div>
<div id="feedback-overview-description">
<div id="feedback-overview-description-text"><h3>{% trans 'Description' %}</h3>
<h3 class="feedback-additional">{% trans 'Additional info' %}</h3>
<div id="feedback-additional-none"><span>{% trans 'None' %}</span></div>
<div id="feedback-browser-info"><span>{% trans 'Browser Info' %}</span></div>
<div id="feedback-page-info"><span>{% trans 'Page Info' %}</span></div>
<div id="feedback-page-structure"><span>{% trans 'Page Structure' %}</span></div>
</div>
</div>
<div id="feedback-overview-screenshot"><h3>{% trans 'Screenshot' %}</h3></div>
<div class="feedback-buttons">
<button id="feedback-submit" class="feedback-submit-btn feedback-btn-blue">{% trans 'Submit' %}</button>
<button id="feedback-overview-back" class="feedback-back-btn feedback-btn-gray">{% trans 'Back' %}</button>
</div>
<div id="feedback-overview-error">{% trans 'Please enter a description.' %}</div>
<div class="feedback-wizard-close"></div>
</div>
7 changes: 7 additions & 0 deletions feedback/templates/tpl-submit-error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% load i18n %}
<div id="feedback-submit-error">
<div class="feedback-logo">{% trans 'Feedback' %}</div>
<p>{% trans 'Sadly an error occurred while sending your feedback. Please try again.' %}</p>
<button class="feedback-close-btn feedback-btn-blue">{% trans 'OK' %}</button>
<div class="feedback-wizard-close"></div>
</div>
8 changes: 8 additions & 0 deletions feedback/templates/tpl-submit-success.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% load i18n %}
<div id="feedback-submit-success">
<div class="feedback-logo">{% trans 'Feedback' %}</div>
<p>{% trans 'Thank you for your feedback. We value every piece of feedback we receive.'%}</p>
<p>{% trans 'We cannot respond individually to every one, but we will use your comments as we strive to improve your experience.' %}</p>
<button class="feedback-close-btn feedback-btn-blue">{% trans 'OK' %}</button>
<div class="feedback-wizard-close"></div>
</div>
3 changes: 3 additions & 0 deletions feedback/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
9 changes: 9 additions & 0 deletions feedback/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.conf.urls import url
from .views import FeedBackView


app_name = 'feedback'

urlpatterns = [
url('post_feedback/', FeedBackView.as_view(), name='post_feedback'),
]
41 changes: 41 additions & 0 deletions feedback/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import json
from base64 import b64decode

from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
from django.core.files.base import ContentFile
from django.utils.crypto import get_random_string
from django.views.generic import View

from mainapp.redis_queue import sms_queue
from mainapp.sms_handler import send_confirmation_sms
from .forms import FeedbackForm


class FeedBackView(View):
def post(self, request):
if request.method == 'POST' and request.is_ajax():

feedback = json.loads(request.POST["feedback"])
data = {'url': feedback['url'], 'browser': json.dumps(feedback['browser']), 'comment': feedback['note'],
'phone': feedback.get('phone')}
if request.user.id:
data['user'] = request.user.id
imgstr = feedback['img'].split(';base64,')[1]
file = {'screenshot': ContentFile(b64decode(imgstr), name="screenshot_" + get_random_string(6) + ".png")}
form = FeedbackForm(data, file)

# check whether it's valid and send sms confirmation:
if form.is_valid():
f = form.save()
confirmation_message = (
"Your feedback has been received, we will follow up soon. Thanks for your valuable feedback."
)
sms_queue.enqueue(
send_confirmation_sms, f.phone, confirmation_message
)
return HttpResponse(status=200)
else:
return JsonResponse({'error': dict(form.errors)})

else:
return HttpResponseBadRequest()
26 changes: 16 additions & 10 deletions floodrelief/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ def get_list(text):
# Application definition

INSTALLED_APPS = [
# 'sslserver',
# 'autofixture',
'feedback',
'mainapp.apps.MainappConfig',
'django.contrib.admin',
'django.contrib.auth',
Expand Down Expand Up @@ -203,22 +206,23 @@ def get_list(text):
os.path.join(BASE_DIR, 'static'),
)
bucket_name = os.environ.get('AWS_STORAGE_BUCKET_NAME')
S3_URL = "https://{}.s3.ap-south-1.amazonaws.com".format(bucket_name,)
S3_URL = "https://{}.s3.ap-south-1.amazonaws.com".format(bucket_name, )


if os.environ.get('USE_S3'):
AWS_STORAGE_BUCKET_NAME=bucket_name
AWS_ACCESS_KEY_ID=os.environ.get("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY=os.environ.get("AWS_SECRET_ACCESS_KEY")
AWS_QUERYSTRING_AUTH=False
if DEBUG:
MEDIA_URL = '/media/'
elif os.environ.get('USE_S3'):
AWS_STORAGE_BUCKET_NAME = bucket_name
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")
AWS_QUERYSTRING_AUTH = False
MEDIA_URL = S3_URL + "/media/"
DEFAULT_FILE_STORAGE="storages.backends.s3boto3.S3Boto3Storage"
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
else:
MEDIA_URL = '/media/'
ADMIN_SITE_HEADER = "Keralarescue Dashboard"

MEDIA_ROOT = 'media'

#JWT REST Auth for API
# JWT REST Auth for API
REST_USE_JWT = True
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=20)
Expand All @@ -236,3 +240,5 @@ def get_list(text):
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
}

ADMIN_SITE_HEADER = "Keralarescue Dashboard"
1 change: 1 addition & 0 deletions floodrelief/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

urlpatterns = [
path('', include('mainapp.urls')),
path('fb/', include("feedback.urls")),
path('admin/', admin.site.urls),
path('login/', auth_views.LoginView.as_view()),
path('api/1/rest-auth/', include('rest_auth.urls')),
Expand Down
5 changes: 1 addition & 4 deletions mainapp/sms_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
SMS_PASSWORD = os.environ.get("SMS_PASSWORD")


def send_confirmation_sms(phone_number):
confirmation_message = (
"Your rescue request has been registered, we will follow up soon. Stay safe"
)
def send_confirmation_sms(phone_number, confirmation_message):
api_url = "{}?username={}&password={}&message={}&numbers={}&senderid=KSITMK".format(
SMS_ENDPOINT,
SMS_USER,
Expand Down
Loading