Skip to content

Commit

Permalink
Merge pull request #33909 from dimagi/mk/3235-edit-banner
Browse files Browse the repository at this point in the history
Add ability to edit domain banners / alerts
  • Loading branch information
mkangia authored Jan 4, 2024
2 parents a052ac1 + 5be5716 commit c987a26
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 25 deletions.
6 changes: 1 addition & 5 deletions corehq/apps/domain/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2734,11 +2734,7 @@ def __init__(self, request, *args, **kwargs):
self.fields['end_time'].help_text = datetime_local_widget_helptext

self.helper = hqcrispy.HQFormHelper(self)
self.helper.layout = Layout(
crispy.Fieldset(
_('Add New Alert'),
*self.fields
),
self.helper.layout.append(
hqcrispy.FormActions(
StrictButton(
_('Save'),
Expand Down
13 changes: 11 additions & 2 deletions corehq/apps/domain/static/domain/js/manage_alerts.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
hqDefine("domain/js/manage_alerts",[
'jquery',
'knockout',
'underscore',
'hqwebapp/js/initial_page_data',
], function ($, initialPageData) {
], function ($, ko, _, initialPageData) {

var domainAlert = function (options) {
var self = ko.mapping.fromJS(options);
self.editUrl = initialPageData.reverse('domain_edit_alert', self.id());
return self;
};

$(function () {
$('#ko-alert-container').koApplyBindings({
'alerts': initialPageData.get('alerts'),
'alerts': _.map(initialPageData.get('alerts'), domainAlert),
});
});
});
12 changes: 12 additions & 0 deletions corehq/apps/domain/templates/domain/admin/edit_alert.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% extends "hqwebapp/bootstrap3/base_section.html" %}
{% load crispy_forms_tags %}
{% load i18n %}

{% block page_content %}
<form class="form form-horizontal" method="post">
<fieldset>
<legend>{% trans 'Edit Alert' %}</legend>
{% crispy form %}
</fieldset>
</form>
{% endblock %}
15 changes: 11 additions & 4 deletions corehq/apps/domain/templates/domain/admin/manage_alerts.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@

{% block page_content %}
{% initial_page_data 'alerts' alerts %}
{% registerurl 'domain_edit_alert' domain '---' %}

<form class="form form-horizontal" method="post">
{% crispy form %}
<fieldset>
<legend>{% trans 'Add New Alert' %}</legend>
{% crispy form %}
</fieldset>
</form>
<div id="ko-alert-container">
<h3>
Expand Down Expand Up @@ -37,7 +41,7 @@ <h3>
{% trans "Activate or De-activate" %}
</th>
<th>
{% trans "Delete" %}
{% trans "Actions" %}
</th>
</tr>
</thead>
Expand All @@ -62,7 +66,7 @@ <h3>
class="btn btn-primary"
name="command"
value="activate"
data-bind="visible: !active">
data-bind="hidden: active">
<span>{% trans "Activate Alert" %}</span>
</button>
<button type="submit"
Expand All @@ -75,7 +79,10 @@ <h3>
</form>
</td>
<td>
<form action="{% url 'delete_domain_alert' domain %}" method="post">
<a class="btn btn-default" data-bind="attr: {href: editUrl}" style="float: left; margin-right: 5px;">
{% trans "Edit" %}
</a>
<form action="{% url 'delete_domain_alert' domain %}" method="post" style="float: left">
{% csrf_token %}
<input name="alert_id"
type="hidden"
Expand Down
65 changes: 64 additions & 1 deletion corehq/apps/domain/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from corehq.apps.accounting.utils import clear_plan_version_cache
from corehq.apps.app_manager.models import Application
from corehq.apps.domain.models import Domain
from corehq.apps.domain.views.settings import ManageDomainAlertsView, MAX_ACTIVE_ALERTS
from corehq.apps.domain.views.settings import EditDomainAlertView, ManageDomainAlertsView, MAX_ACTIVE_ALERTS
from corehq.apps.hqwebapp.models import Alert
from corehq.apps.users.models import WebUser
from corehq.motech.models import ConnectionSettings
Expand Down Expand Up @@ -388,6 +388,69 @@ def test_delete(self):
self.assertEqual(response.status_code, 302)


class TestEditDomainAlertView(TestBaseDomainAlertView):
@classmethod
def setUpClass(cls):
super().setUpClass()

cls.url = reverse(EditDomainAlertView.urlname, kwargs={
'domain': cls.domain_name, 'alert_id': cls.domain_alert.id
})

def test_feature_flag_access_only(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 404)

@flag_enabled('CUSTOM_DOMAIN_BANNER_ALERTS')
def test_only_domain_alerts_accessible(self):
url = reverse(EditDomainAlertView.urlname, kwargs={
'domain': self.domain_name, 'alert_id': self.other_domain_alert.id
})

with self.assertRaisesMessage(AssertionError, 'Alert not found'):
self.client.get(url)

@flag_enabled('CUSTOM_DOMAIN_BANNER_ALERTS')
def test_only_domain_alerts_accessible_for_update(self):
url = reverse(EditDomainAlertView.urlname, kwargs={
'domain': self.domain_name, 'alert_id': self.other_domain_alert.id
})
response = self.client.post(url, data={'text': 'Bad text'})

messages = list(get_messages(response.wsgi_request))
self.assertEqual(messages[0].message, 'Alert not found!')
self.assertEqual(response.status_code, 302)

@flag_enabled('CUSTOM_DOMAIN_BANNER_ALERTS')
def test_updating_alert(self):
text = self.domain_alert.text + ". Updated!"
response = self.client.post(
self.url,
data={
'text': text,
},
)

messages = list(get_messages(response.wsgi_request))
self.assertEqual(messages[0].message, 'Alert saved!')
self.assertEqual(response.status_code, 302)
self.domain_alert.refresh_from_db()
self.assertEqual(self.domain_alert.text, 'Test Alert 1!. Updated!')

@flag_enabled('CUSTOM_DOMAIN_BANNER_ALERTS')
def test_updating_alert_with_errors(self):
response = self.client.post(
self.url,
data={
'text': '',
},
)

messages = list(get_messages(response.wsgi_request))
self.assertEqual(messages[0].message, 'There was an error saving your alert. Please try again!')
self.assertEqual(response.status_code, 200)


@contextmanager
def domain_fixture(domain_name, allow_domain_requests=False):
domain = Domain(name=domain_name, is_active=True)
Expand Down
2 changes: 2 additions & 0 deletions corehq/apps/domain/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
CaseSearchConfigView,
DefaultProjectSettingsView,
EditBasicProjectInfoView,
EditDomainAlertView,
EditMyProjectSettingsView,
EditPrivacySecurityView,
FeaturePreviewsView,
Expand Down Expand Up @@ -192,6 +193,7 @@
name=EditInternalCalculationsView.urlname),
url(r'^internal/calculated_properties/$', calculated_properties, name='calculated_properties'),
url(r'^previews/$', FeaturePreviewsView.as_view(), name=FeaturePreviewsView.urlname),
url(r'^alerts/edit/(?P<alert_id>[\w\-]+)/$', EditDomainAlertView.as_view(), name=EditDomainAlertView.urlname),
url(r'^alerts/$', ManageDomainAlertsView.as_view(), name=ManageDomainAlertsView.urlname),
url(r'^alerts/delete/$', delete_domain_alert, name='delete_domain_alert'),
url(r'^alerts/update_status/$', update_domain_alert_status, name='update_domain_alert_status'),
Expand Down
117 changes: 104 additions & 13 deletions corehq/apps/domain/views/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,20 @@ class ManageDomainMobileWorkersView(ManageMobileWorkersMixin, BaseAdminProjectSe

@method_decorator([toggles.CUSTOM_DOMAIN_BANNER_ALERTS.required_decorator(),
require_can_manage_domain_alerts], name='dispatch')
class ManageDomainAlertsView(BaseProjectSettingsView):
class BaseDomainAlertsView(BaseProjectSettingsView):
@staticmethod
def _convert_user_time_to_server_time(timestamp, timezone):
return UserTime(
timestamp,
tzinfo=pytz.timezone(timezone)
).server_time()

@staticmethod
def _convert_server_time_to_user_time(timestamp, timezone):
return ServerTime(timestamp).user_time(pytz.timezone(timezone))


class ManageDomainAlertsView(BaseDomainAlertsView):
template_name = 'domain/admin/manage_alerts.html'
urlname = 'domain_manage_alerts'
page_title = gettext_lazy("Manage Project Alerts")
Expand All @@ -542,10 +555,14 @@ def page_context(self):
'form': self.form,
'alerts': [
{
'start_time': ServerTime(alert.start_time).user_time(pytz.timezone(alert.timezone))
.ui_string() if alert.start_time else None,
'end_time': ServerTime(alert.end_time).user_time(pytz.timezone(alert.timezone))
.ui_string() if alert.end_time else None,
'start_time': (
self._convert_server_time_to_user_time(alert.start_time, alert.timezone).ui_string()
if alert.start_time else None
),
'end_time': (
self._convert_server_time_to_user_time(alert.end_time, alert.timezone).ui_string()
if alert.end_time else None
),
'active': alert.active,
'html': alert.html,
'id': alert.id,
Expand Down Expand Up @@ -575,8 +592,14 @@ def _create_alert(self):
end_time = self.form.cleaned_data['end_time']
timezone = self.request.project.default_timezone

start_time = self._convert_timestamp_to_utc(start_time, timezone) if start_time else None
end_time = self._convert_timestamp_to_utc(end_time, timezone) if end_time else None
start_time = (
self._convert_user_time_to_server_time(start_time, timezone).done()
if start_time else None
)
end_time = (
self._convert_user_time_to_server_time(end_time, timezone).done()
if end_time else None
)

Alert.objects.create(
created_by_domain=self.domain,
Expand All @@ -588,12 +611,80 @@ def _create_alert(self):
created_by_user=self.request.couch_user.username,
)

@staticmethod
def _convert_timestamp_to_utc(timestamp, timezone):
return UserTime(
timestamp,
tzinfo=pytz.timezone(timezone)
).server_time().done()

class EditDomainAlertView(BaseDomainAlertsView):
template_name = 'domain/admin/edit_alert.html'
urlname = 'domain_edit_alert'
page_title = gettext_lazy("Edit Project Alert")

@property
@memoized
def page_url(self):
return reverse(ManageDomainAlertsView.urlname, args=[self.domain])

@property
def page_context(self):
return {
'form': self.form
}

@cached_property
def form(self):
if self.request.method == 'POST':
return DomainAlertForm(self.request, self.request.POST)

alert = self._get_alert()
assert alert, "Alert not found"

initial = {
'text': alert.text,
'start_time': (
self._convert_server_time_to_user_time(alert.start_time, alert.timezone).done()
if alert.start_time else None
),
'end_time': (
self._convert_server_time_to_user_time(alert.end_time, alert.timezone).done()
if alert.end_time else None
),
}
return DomainAlertForm(self.request, initial=initial)

def _get_alert(self):
try:
return Alert.objects.get(created_by_domain=self.domain, pk=self.kwargs.get('alert_id'))
except Alert.DoesNotExist:
return None

def post(self, request, *args, **kwargs):
if self.form.is_valid():
alert = self._get_alert()
if not alert:
messages.error(request, _("Alert not found!"))
else:
self._update_alert(alert)
messages.success(request, _("Alert saved!"))
else:
messages.error(request, _("There was an error saving your alert. Please try again!"))
return self.get(request, *args, **kwargs)
return HttpResponseRedirect(self.page_url)

def _update_alert(self, alert):
alert.text = self.form.cleaned_data['text']

start_time = self.form.cleaned_data['start_time']
end_time = self.form.cleaned_data['end_time']
timezone = self.request.project.default_timezone

alert.start_time = (
self._convert_user_time_to_server_time(start_time, timezone).done()
if start_time else None
)
alert.end_time = (
self._convert_user_time_to_server_time(end_time, timezone).done()
if end_time else None
)

alert.save()


@toggles.CUSTOM_DOMAIN_BANNER_ALERTS.required_decorator()
Expand Down

0 comments on commit c987a26

Please sign in to comment.