From 4470b4bae77b244c8555123e2e994a76db51cb73 Mon Sep 17 00:00:00 2001 From: Igor Santos Date: Tue, 26 Sep 2023 12:27:42 -0300 Subject: [PATCH 1/9] feat(cms): add tutorial to config new site --- TUTORIAIS.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 TUTORIAIS.md diff --git a/TUTORIAIS.md b/TUTORIAIS.md new file mode 100644 index 00000000..f0756614 --- /dev/null +++ b/TUTORIAIS.md @@ -0,0 +1,21 @@ +### Etapas para cadastrar um site + +#### Adicionar registro de domínio + +Variaveis: +- Comunidade ID (2548): Obrigatório +- Endereço de domínio do SITE (busao.localhost:8000): Obrigatório +- Comentário: Opcional + +``` +insert into dns_hosted_zones(community_id, domain_name, comment) values(2548, 'busao.localhost:8000', 'Criado manualmente'); +``` + +#### Configurar ALLOWED_HOSTS e novo Site no projeto + +Variaveis: +- Endereço de domínio do SITE (busao.localhost:8000): Obrigatório + +Adicionar Endereço de domínio na variavel ALLOWED_HOSTS do serviço no Portainer. + +Criar novo Site através do Administrador (Linkar tutorial de inserir Novo Site) From c23aeb1208e99c52a00b9696c799b790c5013d22 Mon Sep 17 00:00:00 2001 From: Igor Santos Date: Tue, 26 Sep 2023 13:55:33 -0300 Subject: [PATCH 2/9] feat(pressure): add submit form direct on bonde by api-graphql --- app/contrib/actions/pressure/forms.py | 45 ++++++++++++++++++++++----- app/contrib/bonde/models.py | 5 +-- app/project/settings/base.py | 4 +++ app/requirements.txt | 1 + 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/app/contrib/actions/pressure/forms.py b/app/contrib/actions/pressure/forms.py index 4cdec9de..1171f71a 100644 --- a/app/contrib/actions/pressure/forms.py +++ b/app/contrib/actions/pressure/forms.py @@ -1,6 +1,10 @@ import json +import jwt +import requests from django import forms +from django.db import transaction +from django.conf import settings from contrib.bonde.forms import ReferenceBaseModelForm from tailwind.forms import StyledBaseForm @@ -28,6 +32,7 @@ class PressureAjaxForm(StyledBaseForm): class Meta(StyledBaseForm.Meta): readonly_fields = ["email_subject", "email_body"] + @transaction.atomic def submit(self): activist = { "email": self.cleaned_data["email_address"], @@ -39,13 +44,37 @@ def submit(self): "email_subject": self.cleaned_data["email_subject"], "email_body": self.cleaned_data["email_body"], "form_data": json.dumps(self.cleaned_data), + "token": jwt.encode({}, settings.BONDE_ACTION_SECRET_KEY), } - print( - "Submitting ->>", - { - "activist": activist, - "input": input, - "widget_id": self.cleaned_data["reference_id"], - }, - ) + query = """ + mutation Pressure($activist: ActivistInput!, $input: EmailPressureInput, $widget_id: Int!) { + create_email_pressure( + activist: $activist, + widget_id: $widget_id, + input: $input + ) { + data + } + } + """ + variables = { + "activist": activist, + "input": input, + "widget_id": self.cleaned_data["reference_id"], + } + + resp = requests.post(settings.BONDE_ACTION_API_URL, json={"query": query, "variables": variables}) + if resp.status_code == 200: + print(resp.json()) + else: + raise Exception("Query failed to run by returning code of {}. {}".format(resp.status_code, query)) + + # print( + # "Submitting ->>", + # { + # "activist": activist, + # "input": input, + # "widget_id": self.cleaned_data["reference_id"], + # }, + # ) diff --git a/app/contrib/bonde/models.py b/app/contrib/bonde/models.py index 0ce375a1..1a57d796 100644 --- a/app/contrib/bonde/models.py +++ b/app/contrib/bonde/models.py @@ -96,9 +96,10 @@ def __str__(self): return self.name def get_signature(self): + signature = self.signature or {} return { - "name": self.signature.get("name", self.name), - "url": self.signature.get("url", "#"), + "name": signature.get("name", self.name), + "url": signature.get("url", "#"), } diff --git a/app/project/settings/base.py b/app/project/settings/base.py index 042eb04d..c1fa9b34 100644 --- a/app/project/settings/base.py +++ b/app/project/settings/base.py @@ -148,6 +148,10 @@ "contrib.bonde.router.AuthRouter", ] +BONDE_ACTION_API_URL = env.str("BONDE_ACTION_API_URL", "http://api-graphql.localhost/v1/graphql") + +BONDE_ACTION_SECRET_KEY = env.str("BONDE_ACTION_SECRET_KEY", "") + # Password validation # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators diff --git a/app/requirements.txt b/app/requirements.txt index b0c725c2..0551fc0a 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -18,3 +18,4 @@ whitenoise svglib reportlab django-social-share +pyjwt From 154e9a3db33d0212321791c734d7e68562fac33c Mon Sep 17 00:00:00 2001 From: miguelzinh3 Date: Tue, 18 Jul 2023 21:24:01 -0300 Subject: [PATCH 3/9] feat(pressure): add success message template --- .../pressure/static/pressure/js/pressure.js | 31 +++++++++++++--- .../templates/pressure/pressure_plugin.html | 22 ++++++------ .../templates/pressure/pressure_success.html | 33 +++++++++++++++++ app/contrib/actions/pressure/views.py | 35 +++++++++---------- 4 files changed, 87 insertions(+), 34 deletions(-) create mode 100644 app/contrib/actions/pressure/templates/pressure/pressure_success.html diff --git a/app/contrib/actions/pressure/static/pressure/js/pressure.js b/app/contrib/actions/pressure/static/pressure/js/pressure.js index da1cca38..866a9d62 100644 --- a/app/contrib/actions/pressure/static/pressure/js/pressure.js +++ b/app/contrib/actions/pressure/static/pressure/js/pressure.js @@ -3,11 +3,31 @@ $(function () { $(".pressure-plugin form").on("submit", function (evt) { - const $form = $(this); + const $form = $(this); - function handleResponse(data) { - if (data.success) { - alert("success"); + function showTooltip() { + const tooltip = $("#copyToClipboardTooltip"); + tooltip.toggleClass("tooltip-open hover:before:block hover:after:block"); + + // hide after 3 seconds + window.setTimeout(function () { + tooltip.removeClass("tooltip-open hover:before:block hover:after:block"); + }, 2000); + } + + function handleResponse(data) { + if (data.success) { + $("#pressureWrapper").empty(); + $("#pressureWrapper").html(data.html); + + $("#copyToClipboard").on("click", function () { + const textToCopy = window.location.href; + + navigator.clipboard + .writeText(textToCopy) + .then(showTooltip) + .catch(() => console.error("Erro ao copiar link, tente novamente.")); + }); window.gtag("event", "form_submit_success", { form_id: $form.attr("id") }); } else { $form.find('.errorlist').empty(); @@ -25,10 +45,11 @@ } evt.preventDefault(); + $("#id_referrer_path").val(window.location.href); $.ajax($form.attr('action'), { type: 'POST', data: $form.serialize(), }).always(handleResponse); }); }); -}(window.jQuery)); \ No newline at end of file +}(window.jQuery)); diff --git a/app/contrib/actions/pressure/templates/pressure/pressure_plugin.html b/app/contrib/actions/pressure/templates/pressure/pressure_plugin.html index 70f983cb..fc912570 100644 --- a/app/contrib/actions/pressure/templates/pressure/pressure_plugin.html +++ b/app/contrib/actions/pressure/templates/pressure/pressure_plugin.html @@ -5,15 +5,15 @@ {% endaddtoblock %} -
+
-

{{settings.title}}

-
+

{{settings.title}}

+
{% with settings.targets|length as targets_size %}

Quem você vai pressionar? ({{targets_size}} alvo{% if targets_size > 1 %}s{% endif %})

-
    +
      {% for target in settings.targets %} -
    • +
    • {{ target.name }}

      {{ target.email }}

    • @@ -21,11 +21,11 @@

      {{settings.title}}

    {% endwith %}
- +
{% csrf_token %} {% for field in form.visible_fields %} -
+
{{ field }} {% if field.label %} @@ -37,13 +37,13 @@

{{settings.title}}

{% endfor %} {% for hidden_field in form.hidden_fields %}{{hidden_field}}{% endfor %} -
- +
+

Ao inserir seus dados, você concorda em ter seus dados compartilhados com os organizadores dessa página e aceita receber emails de atualização, conforme descrito na política de privacidade. Você pode cancelar o recebimento desses e-mails a qualquer momento.

-

{{ size }} {{ settings.count }}

+

{{ size }} {{ settings.count }}

-
\ No newline at end of file +
diff --git a/app/contrib/actions/pressure/templates/pressure/pressure_success.html b/app/contrib/actions/pressure/templates/pressure/pressure_success.html new file mode 100644 index 00000000..9f1355bf --- /dev/null +++ b/app/contrib/actions/pressure/templates/pressure/pressure_success.html @@ -0,0 +1,33 @@ +
+

Valeu, {{form_data.name}}! 🎉
Sua pressão chegou nos deputados

+

Agora compartilhe nas redes para mais gente fazer parte desse movimento:

+ +

{{ form_data.total_actions }}

+
diff --git a/app/contrib/actions/pressure/views.py b/app/contrib/actions/pressure/views.py index 1d301687..7fc8035a 100644 --- a/app/contrib/actions/pressure/views.py +++ b/app/contrib/actions/pressure/views.py @@ -1,11 +1,11 @@ import json -from django.http import HttpResponse +from django.http import JsonResponse from django.views.generic import FormView +from django.shortcuts import render from .forms import PressureAjaxForm - class AjaxableResponseMixin(object): """ Mixin to add AJAX support to a form. @@ -13,29 +13,28 @@ class AjaxableResponseMixin(object): """ def render_to_json_response(self, context, **response_kwargs): - data = json.dumps(context) - response_kwargs["content_type"] = "application/json" - return HttpResponse(data, **response_kwargs) + return JsonResponse(context, **response_kwargs) def form_invalid(self, form): - response = super(AjaxableResponseMixin, self).form_invalid(form) + response = super().form_invalid(form) if self.request.is_ajax(): - return self.render_to_json_response(form.errors) # , status=400) + return self.render_to_json_response({'success': False, 'errors': form.errors}, status=400) else: return response def form_valid(self, form): - # We make sure to call the parent's form_valid() method because - # it might do some processing (in the case of CreateView, it will - # call form.save() for example). - response = super(AjaxableResponseMixin, self).form_valid(form) - if self.request.is_ajax(): - data = { - "success": True, - } - return self.render_to_json_response(data) - else: - return response + # We make sure to call the parent's form_valid() method because + # it might do some processing (in the case of CreateView, it will + # call form.save() for example). + response = super().form_valid(form) + if self.request.is_ajax(): + data = { + 'success': True, + 'html': render(self.request, 'pressure/pressure_success.html', {"form_data": form.cleaned_data}).content.decode('utf-8') + } + return self.render_to_json_response(data) + else: + return response class PressureFormAjaxView(AjaxableResponseMixin, FormView): From 0cd5ec4d6b212c7d02d14cc9e02c8aabae182d8b Mon Sep 17 00:00:00 2001 From: Igor Santos Date: Tue, 26 Sep 2023 15:09:27 -0300 Subject: [PATCH 4/9] fix(pressure): add referrer_path to shared buttons on post action --- app/contrib/actions/pressure/cms_plugins.py | 7 ++++++- app/contrib/actions/pressure/forms.py | 12 +----------- .../templates/pressure/pressure_success.html | 2 +- app/contrib/actions/pressure/views.py | 9 +++------ 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/app/contrib/actions/pressure/cms_plugins.py b/app/contrib/actions/pressure/cms_plugins.py index 21dd6fed..afcb5eaa 100644 --- a/app/contrib/actions/pressure/cms_plugins.py +++ b/app/contrib/actions/pressure/cms_plugins.py @@ -15,6 +15,7 @@ class PressurePlugin(CMSPluginBase): def render(self, context, instance, placeholder): obj = instance.get_widget() + request = context["request"] initial = ( { "email_subject": obj.settings.get("pressure_subject", ""), @@ -28,7 +29,11 @@ def render(self, context, instance, placeholder): if instance.reference_id: form = PressureAjaxForm( - initial={"reference_id": instance.reference_id, **initial} + initial={ + "reference_id": instance.reference_id, + "referrer_path": f"{request.scheme}://{request.get_host()}{request.path}", + **initial, + } ) context.update( diff --git a/app/contrib/actions/pressure/forms.py b/app/contrib/actions/pressure/forms.py index 1171f71a..f6d2fc81 100644 --- a/app/contrib/actions/pressure/forms.py +++ b/app/contrib/actions/pressure/forms.py @@ -3,7 +3,6 @@ import requests from django import forms -from django.db import transaction from django.conf import settings from contrib.bonde.forms import ReferenceBaseModelForm @@ -22,6 +21,7 @@ class Meta(ReferenceBaseModelForm.Meta): class PressureAjaxForm(StyledBaseForm): reference_id = forms.IntegerField(widget=forms.HiddenInput) + referrer_path = forms.CharField(widget=forms.HiddenInput) email_address = forms.EmailField(label="Seu e-mail") name = forms.CharField(label="Seu nome", max_length=80) @@ -32,7 +32,6 @@ class PressureAjaxForm(StyledBaseForm): class Meta(StyledBaseForm.Meta): readonly_fields = ["email_subject", "email_body"] - @transaction.atomic def submit(self): activist = { "email": self.cleaned_data["email_address"], @@ -69,12 +68,3 @@ def submit(self): print(resp.json()) else: raise Exception("Query failed to run by returning code of {}. {}".format(resp.status_code, query)) - - # print( - # "Submitting ->>", - # { - # "activist": activist, - # "input": input, - # "widget_id": self.cleaned_data["reference_id"], - # }, - # ) diff --git a/app/contrib/actions/pressure/templates/pressure/pressure_success.html b/app/contrib/actions/pressure/templates/pressure/pressure_success.html index 9f1355bf..349c4159 100644 --- a/app/contrib/actions/pressure/templates/pressure/pressure_success.html +++ b/app/contrib/actions/pressure/templates/pressure/pressure_success.html @@ -1,5 +1,5 @@
-

Valeu, {{form_data.name}}! 🎉
Sua pressão chegou nos deputados

+

Valeu, {{form_data.name}}! 🎉
Sua pressão foi enviada.

Agora compartilhe nas redes para mais gente fazer parte desse movimento:

diff --git a/app/contrib/actions/pressure/views.py b/app/contrib/actions/pressure/views.py index 7fc8035a..7b63b9e3 100644 --- a/app/contrib/actions/pressure/views.py +++ b/app/contrib/actions/pressure/views.py @@ -27,7 +27,9 @@ def form_valid(self, form): # it might do some processing (in the case of CreateView, it will # call form.save() for example). response = super().form_valid(form) + if self.request.is_ajax(): + form.submit() data = { 'success': True, 'html': render(self.request, 'pressure/pressure_success.html', {"form_data": form.cleaned_data}).content.decode('utf-8') @@ -49,9 +51,4 @@ class PressureFormAjaxView(AjaxableResponseMixin, FormView): # this plugin is coming from. # def get_success_url(self): - return self.request.path - - def form_valid(self, form): - # AjaxableResponseMixin expects our contact object to be 'self.object'. - self.object = form.submit() - return super(PressureFormAjaxView, self).form_valid(form) + return self.request.path \ No newline at end of file From 94059ee7df4ef7c3d513fa25525c8a46a737689f Mon Sep 17 00:00:00 2001 From: Igor Santos Date: Tue, 26 Sep 2023 16:44:33 -0300 Subject: [PATCH 5/9] feat(styleguide): add classes to color social shared buttons --- .../templates/pressure/pressure_success.html | 8 +++--- app/tailwind/plugins/socialbuttons.plugin.js | 28 +++++++++++++++++++ app/tailwind/tailwind.page.config.js | 24 ++++++++++++++-- app/tailwind/templates/tailwind.html | 2 +- 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 app/tailwind/plugins/socialbuttons.plugin.js diff --git a/app/contrib/actions/pressure/templates/pressure/pressure_success.html b/app/contrib/actions/pressure/templates/pressure/pressure_success.html index 349c4159..6f1efd4c 100644 --- a/app/contrib/actions/pressure/templates/pressure/pressure_success.html +++ b/app/contrib/actions/pressure/templates/pressure/pressure_success.html @@ -2,26 +2,26 @@

Valeu, {{form_data.name}}! 🎉
Sua pressão foi enviada.

Agora compartilhe nas redes para mais gente fazer parte desse movimento:

- + COMPARTILHAR NO WHATSAPP - + COMPARTILHAR NO TWITTER - + COMPARTILHAR NO FACEBOOK
- +

    Ao inserir seus dados, você concorda em ter seus dados compartilhados com os organizadores dessa página e aceita receber emails de atualização, conforme descrito na política de privacidade. Você pode cancelar o recebimento desses e-mails a qualquer momento.

    diff --git a/app/contrib/actions/pressure/views.py b/app/contrib/actions/pressure/views.py index 7b63b9e3..64071fef 100644 --- a/app/contrib/actions/pressure/views.py +++ b/app/contrib/actions/pressure/views.py @@ -29,12 +29,16 @@ def form_valid(self, form): response = super().form_valid(form) if self.request.is_ajax(): - form.submit() - data = { - 'success': True, - 'html': render(self.request, 'pressure/pressure_success.html', {"form_data": form.cleaned_data}).content.decode('utf-8') - } - return self.render_to_json_response(data) + try: + form.submit() + data = { + 'success': True, + 'html': render(self.request, 'pressure/pressure_success.html', {"form_data": form.cleaned_data}).content.decode('utf-8') + } + return self.render_to_json_response(data) + except Exception as err: + form.add_error(None, err) + return self.render_to_json_response({ 'success': False, 'errors': form.errors }, status=500) else: return response From 51cb81f0a4f322a8ac477a812bbb5e4c1bef6fea Mon Sep 17 00:00:00 2001 From: Igor Santos Date: Wed, 27 Sep 2023 11:35:07 -0300 Subject: [PATCH 8/9] feat(ci): add bonde actions env to deploy app --- deploy/docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index a992a6ca..56005a0a 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -14,6 +14,8 @@ services: - RECAPTCHA_PUBLIC_KEY=${RECAPTCHA_PUBLIC_KEY} - RECAPTCHA_PRIVATE_KEY=${RECAPTCHA_PRIVATE_KEY} - DISABLE_RECAPTCHA=${DISABLE_RECAPTCHA} + - BONDE_ACTION_API_URL=${BONDE_ACTION_API_URL} + - BONDE_ACTION_SECRET_KEY=${BONDE_ACTION_SECRET_KEY} labels: - traefik.enable=true - traefik.http.routers.cms.priority=10 From ffee7ca1173b2ec11673a8482d26ebb7ba13ce1a Mon Sep 17 00:00:00 2001 From: Igor Santos Date: Wed, 27 Sep 2023 11:57:44 -0300 Subject: [PATCH 9/9] fix(pressure): split js functions to fail and success pressure --- .../pressure/static/pressure/js/pressure.js | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/app/contrib/actions/pressure/static/pressure/js/pressure.js b/app/contrib/actions/pressure/static/pressure/js/pressure.js index e5152a36..bcde4b59 100644 --- a/app/contrib/actions/pressure/static/pressure/js/pressure.js +++ b/app/contrib/actions/pressure/static/pressure/js/pressure.js @@ -15,50 +15,49 @@ }, 2000); } - function handleResponse({ status, responseJSON }) { - if (status === 200 && responseJSON.success) { - $("#pressureWrapper").empty(); - $("#pressureWrapper").html(data.html); + function failResponse({ responseJSON }) { + $form.find('.errorlist').empty(); + + $.each(responseJSON.errors, function (key, value) { + if (key === '__all__') { + const $errorlist = $form.find("[type='submit'] + .errorlist"); + $errorlist.html( + $.each(value, function (item) { + return "
  • " + item + "
  • " + }) + ); + } else { + const $field = $form.find("input[name=" + key + "]").first(); + $field.parents(".form-control").find(".errorlist").html( + $.each(value, function (item) { + return "
  • " + item + "
  • " + }) + ); + } + }); + window.gtag("event", "form_submit_failed", { form_id: $form.attr("id") }); + } - $("#copyToClipboard").on("click", function () { - const textToCopy = window.location.href; + function doneResponse({ success, html }) { + $("#pressureWrapper").empty(); + $("#pressureWrapper").html(html); - navigator.clipboard - .writeText(textToCopy) - .then(showTooltip) - .catch(() => console.error("Erro ao copiar link, tente novamente.")); - }); - window.gtag("event", "form_submit_success", { form_id: $form.attr("id") }); - } else { - $form.find('.errorlist').empty(); + $("#copyToClipboard").on("click", function () { + const textToCopy = window.location.href; - $.each(responseJSON.errors, function (key, value) { - if (key === '__all__') { - const $errorlist = $form.find("[type='submit'] + .errorlist"); - $errorlist.html( - $.each(value, function (item) { - return "
  • " + item + "
  • " - }) - ); - } else { - const $field = $form.find("input[name=" + key + "]").first(); - $field.parents(".form-control").find(".errorlist").html( - $.each(value, function (item) { - return "
  • " + item + "
  • " - }) - ); - } - }); - window.gtag("event", "form_submit_failed", { form_id: $form.attr("id") }); - } + navigator.clipboard + .writeText(textToCopy) + .then(showTooltip) + .catch(() => console.error("Erro ao copiar link, tente novamente.")); + }); + window.gtag("event", "form_submit_success", { form_id: $form.attr("id") }); } evt.preventDefault(); $("#id_referrer_path").val(window.location.href); - $.ajax($form.attr('action'), { - type: 'POST', - data: $form.serialize(), - }).always(handleResponse); + $.ajax($form.attr('action'), { type: 'POST', data: $form.serialize() }) + .fail(failResponse) + .done(doneResponse); }); }); }(window.jQuery));