diff --git a/opentech/apply/determinations/migrations/0008_rename_more_info.py b/opentech/apply/determinations/migrations/0008_rename_more_info.py new file mode 100644 index 0000000000000000000000000000000000000000..f397dc4d8fad336cff8bdb9bde8b5ed0e9c9e354 --- /dev/null +++ b/opentech/apply/determinations/migrations/0008_rename_more_info.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.11 on 2019-10-08 12:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('determinations', '0007_add_determinationformsettings'), + ] + + operations = [ + migrations.AlterField( + model_name='determination', + name='outcome', + field=models.IntegerField(choices=[(0, 'Dismissed'), (1, 'More information requested'), (2, 'Approved')], default=1, verbose_name='Determination'), + ), + ] diff --git a/opentech/apply/determinations/models.py b/opentech/apply/determinations/models.py index 1e5d2b023ce467a423db6cfd3b62e9c61d75d52b..9769b088a794f131dee14aed3d9124da65233527 100644 --- a/opentech/apply/determinations/models.py +++ b/opentech/apply/determinations/models.py @@ -17,7 +17,7 @@ ACCEPTED = 2 DETERMINATION_CHOICES = ( (REJECTED, _('Dismissed')), - (NEEDS_MORE_INFO, _('Needs more info')), + (NEEDS_MORE_INFO, _('More information requested')), (ACCEPTED, _('Approved')), ) diff --git a/opentech/apply/funds/forms.py b/opentech/apply/funds/forms.py index 5a123b3c43e1478062d942df38ffc0c758ad9243..df7ce6d8a9e0446ae2f674598dc59d17d65d6e06 100644 --- a/opentech/apply/funds/forms.py +++ b/opentech/apply/funds/forms.py @@ -17,7 +17,39 @@ from .widgets import Select2MultiCheckboxesWidget, MetaCategorySelect2Widget from .workflow import get_action_mapping -class ProgressSubmissionForm(forms.ModelForm): +class ApplicationSubmissionModelForm(forms.ModelForm): + """ + Application Submission model's save method performs several operations + which are not required in forms which update fields like status, partners etc. + It also has a side effect of creating a new file uploads every time with long filenames (#1572). + """ + + def save(self, commit=True): + """ + Save this form's self.instance object if commit=True. Otherwise, add + a save_m2m() method to the form which can be called after the instance + is saved manually at a later time. Return the model instance. + https://github.com/django/django/blob/5d9cf79baf07fc4aed7ad1b06990532a65378155/django/forms/models.py#L444 + """ + if self.errors: + raise ValueError( + "The %s could not be %s because the data didn't validate." % ( + self.instance._meta.object_name, + 'created' if self.instance._state.adding else 'changed', + ) + ) + if commit: + # If committing, save the instance and the m2m data immediately. + self.instance.save(skip_custom=True) + self._save_m2m() + else: + # If not committing, add a method to the form to allow deferred + # saving of m2m data. + self.save_m2m = self._save_m2m + return self.instance + + +class ProgressSubmissionForm(ApplicationSubmissionModelForm): action = forms.ChoiceField(label='Take action') class Meta: @@ -59,7 +91,7 @@ class BatchProgressSubmissionForm(forms.Form): return action -class ScreeningSubmissionForm(forms.ModelForm): +class ScreeningSubmissionForm(ApplicationSubmissionModelForm): class Meta: model = ApplicationSubmission @@ -73,7 +105,8 @@ class ScreeningSubmissionForm(forms.ModelForm): self.should_show = True -class UpdateSubmissionLeadForm(forms.ModelForm): +class UpdateSubmissionLeadForm(ApplicationSubmissionModelForm): + class Meta: model = ApplicationSubmission fields = ('lead',) @@ -117,7 +150,7 @@ class BatchUpdateSubmissionLeadForm(forms.Form): return None -class UpdateReviewersForm(forms.ModelForm): +class UpdateReviewersForm(ApplicationSubmissionModelForm): reviewer_reviewers = forms.ModelMultipleChoiceField( queryset=User.objects.reviewers().only('pk', 'full_name'), widget=Select2MultiCheckboxesWidget(attrs={'data-placeholder': 'Reviewers'}), @@ -296,7 +329,7 @@ def make_role_reviewer_fields(): return role_fields -class UpdatePartnersForm(forms.ModelForm): +class UpdatePartnersForm(ApplicationSubmissionModelForm): partner_reviewers = forms.ModelMultipleChoiceField( queryset=User.objects.partners(), widget=Select2MultiCheckboxesWidget(attrs={'data-placeholder': 'Partners'}), @@ -357,7 +390,7 @@ class GroupedModelMultipleChoiceField(forms.ModelMultipleChoiceField): return {'label': super().label_from_instance(obj), 'disabled': not obj.is_leaf()} -class UpdateMetaCategoriesForm(forms.ModelForm): +class UpdateMetaCategoriesForm(ApplicationSubmissionModelForm): meta_categories = GroupedModelMultipleChoiceField( queryset=None, # updated in init method widget=MetaCategorySelect2Widget(attrs={'data-placeholder': 'Meta categories'}), diff --git a/opentech/apply/funds/models/submissions.py b/opentech/apply/funds/models/submissions.py index f003611b2882bdbfd7a396565681d1d24e8fc165..a6df3d877f198d8d3b0281429afd3a173aad5ef7 100644 --- a/opentech/apply/funds/models/submissions.py +++ b/opentech/apply/funds/models/submissions.py @@ -578,10 +578,12 @@ class ApplicationSubmission( if response: self.form_data[field_name] = response - def save(self, *args, update_fields=list(), **kwargs): + def save(self, *args, update_fields=list(), skip_custom=False, **kwargs): if update_fields and 'form_data' not in update_fields: # We don't want to use this approach if the user is sending data return super().save(*args, update_fields=update_fields, **kwargs) + elif skip_custom: + return super().save(*args, **kwargs) if self.is_draft: raise ValueError('Cannot save with draft data') diff --git a/opentech/apply/funds/templates/funds/application_base.html b/opentech/apply/funds/templates/funds/application_base.html index 0ac5533c96f78b7332c0d3a44a06a9b740237d3e..a82e3844f287e327d73fa01d0c8847dc87124434 100644 --- a/opentech/apply/funds/templates/funds/application_base.html +++ b/opentech/apply/funds/templates/funds/application_base.html @@ -26,7 +26,6 @@ {# the page has no open rounds and we arent on a round page #} <h3>{% blocktrans %}Sorry this {{ page|verbose_name }} is not accepting applications at the moment{% endblocktrans %}</h3> {% else%} - <p class="wrapper--error message-no-js js-hidden">You must have Javascript enabled to use this form.</p> <form class="form application-form" action="/test500/" method="POST" enctype="multipart/form-data"> {{ form.media }} {% csrf_token %} @@ -46,6 +45,7 @@ {% endfor %} <button class="link link--button-secondary" type="submit" disabled>{% if page.action_text %}{{ page.action_text|safe }}{% else %}Submit for review{% endif %}</button> </form> + <p class="wrapper--error message-no-js js-hidden">You must have Javascript enabled to use this form.</p> {% endif %} </div> {% endblock %} diff --git a/opentech/apply/funds/workflow.py b/opentech/apply/funds/workflow.py index bb204567ccd1c8e0ffb32551a13996ef73dafd6f..973c73f65c9be0aea2270a50536382b17bf6289b 100644 --- a/opentech/apply/funds/workflow.py +++ b/opentech/apply/funds/workflow.py @@ -799,6 +799,7 @@ DoubleStageDefinition = [ 'transitions': { 'proposal_accepted': 'Accept', 'proposal_rejected': 'Dismiss', + 'post_external_review_discussion': 'Ready For Discussion (back)', }, 'display': 'Ready for Final Determination', 'permissions': hidden_from_applicant_permissions, diff --git a/opentech/public/forms/templates/public_forms/form_page.html b/opentech/public/forms/templates/public_forms/form_page.html index dfe28f87e84cffdd75334cf5e451e157f56c819e..daca59a09842c5ed186fe1828ca6decdd8cc591e 100644 --- a/opentech/public/forms/templates/public_forms/form_page.html +++ b/opentech/public/forms/templates/public_forms/form_page.html @@ -5,7 +5,6 @@ <div class="wrapper wrapper--medium wrapper--light-grey-bg wrapper--form"> <h1>{{ page.title }}</h1> {{ page.intro|richtext }} - <p class="wrapper--error message-no-js js-hidden">You must have Javascript enabled to use this form.</p> <form class="form wagtail-form" action="#" data-actionpath="{% pageurl page %}" method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.media }} @@ -18,6 +17,7 @@ {% endfor %} <button class="link link--button-secondary" type="submit" disabled>Submit</button> </form> + <p class="wrapper--error message-no-js js-hidden">You must have Javascript enabled to use this form.</p> </div> {% endblock %} diff --git a/opentech/public/mailchimp/migrations/0001_add_newsletter_setting.py b/opentech/public/mailchimp/migrations/0001_add_newsletter_setting.py new file mode 100644 index 0000000000000000000000000000000000000000..0fba02a23744a9e84236495a361fda7416c6dde1 --- /dev/null +++ b/opentech/public/mailchimp/migrations/0001_add_newsletter_setting.py @@ -0,0 +1,27 @@ +# Generated by Django 2.1.11 on 2019-10-03 12:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('wagtailcore', '0041_group_collection_permissions_verbose_name_plural'), + ] + + operations = [ + migrations.CreateModel( + name='NewsletterSettings', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('newsletter_title', models.CharField(default='Get the latest internet freedom news', help_text='The title of the newsletter signup form.', max_length=255, verbose_name='Newsletter title')), + ('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.Site')), + ], + options={ + 'verbose_name': 'newsletter settings', + }, + ), + ] diff --git a/opentech/public/mailchimp/models.py b/opentech/public/mailchimp/models.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b337a66a4b9b3af65bc69216040e8679c6c68900 100644 --- a/opentech/public/mailchimp/models.py +++ b/opentech/public/mailchimp/models.py @@ -0,0 +1,21 @@ +from django.db import models + +from wagtail.admin.edit_handlers import FieldPanel +from wagtail.contrib.settings.models import BaseSetting, register_setting + + +@register_setting +class NewsletterSettings(BaseSetting): + class Meta: + verbose_name = 'newsletter settings' + + newsletter_title = models.CharField( + "Newsletter title", + max_length=255, + default='Get the latest internet freedom news', + help_text='The title of the newsletter signup form.', + ) + + panels = [ + FieldPanel('newsletter_title'), + ] diff --git a/opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html b/opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html index 5308980c8f72e8dec699902663a28bc8e6ff0405..63ede4bfd2a6fdd8d1d821f46dff591b26a29d58 100644 --- a/opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html +++ b/opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html @@ -1,5 +1,7 @@ +{% if newsletter_enabled %} {% load static i18n %} -<h4>Get the latest internet freedom news</h4> + +<h4>{{ settings.mailchimp.NewsletterSettings.newsletter_title }}</h4> <form class="form newsletter-form" action="#" data-actionpath="{{ PUBLIC_SITE.root_url }}{% url "newsletter:subscribe" %}" method="post"> <div> {% for field in newsletter_form %} @@ -20,3 +22,6 @@ {% block extra_js %} <script src="{% static 'js/public/protect-form.js' %}"></script> {% endblock %} +{% else %} +Set MAILCHIMP_API_KEY and MAILCHIMP_LIST_ID to activate newsletter form. +{% endif %} diff --git a/opentech/public/utils/context_processors.py b/opentech/public/utils/context_processors.py index 3986dc1e684e0d4fc6e04f5be36c938c32fa4788..dd24c7ced597955b33176573bd81e5599bb7bece 100644 --- a/opentech/public/utils/context_processors.py +++ b/opentech/public/utils/context_processors.py @@ -10,6 +10,7 @@ def global_vars(request): 'APPLY_SITE': ApplyHomePage.objects.first().get_site(), 'PUBLIC_SITE': HomePage.objects.first().get_site(), 'newsletter_form': NewsletterForm(), + 'newsletter_enabled': settings.MAILCHIMP_API_KEY and settings.MAILCHIMP_LIST_ID, 'ORG_LONG_NAME': settings.ORG_LONG_NAME, 'ORG_SHORT_NAME': settings.ORG_SHORT_NAME, 'ORG_EMAIL': settings.ORG_EMAIL, diff --git a/opentech/public/utils/migrations/0003_add_site_logo_setting.py b/opentech/public/utils/migrations/0003_add_site_logo_setting.py new file mode 100644 index 0000000000000000000000000000000000000000..3585554fbb52cb7e072389db5ca1ad935c602f89 --- /dev/null +++ b/opentech/public/utils/migrations/0003_add_site_logo_setting.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1.11 on 2019-10-03 12:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('images', '0003_customimage_drupal_id'), + ('utils', '0002_add_footer_content_setting'), + ] + + operations = [ + migrations.AddField( + model_name='systemmessagessettings', + name='site_logo_default', + field=models.ForeignKey(blank=True, help_text='Default site logo', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage'), + ), + migrations.AddField( + model_name='systemmessagessettings', + name='site_logo_mobile', + field=models.ForeignKey(blank=True, help_text='Mobil site logo (if not set default will be used)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage'), + ), + ] diff --git a/opentech/public/utils/models.py b/opentech/public/utils/models.py index 2d8055aa28dc462541e2ea9365b05283decccfcb..21d4d353f92f625cc55c124a63314c1f3ec70d96 100644 --- a/opentech/public/utils/models.py +++ b/opentech/public/utils/models.py @@ -228,6 +228,24 @@ class SystemMessagesSettings(BaseSetting): class Meta: verbose_name = 'system settings' + site_logo_default = models.ForeignKey( + 'images.CustomImage', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+', + help_text='Default site logo', + ) + + site_logo_mobile = models.ForeignKey( + 'images.CustomImage', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+', + help_text='Mobil site logo (if not set default will be used)', + ) + footer_content = models.TextField( "Footer content", default='<p>Configure this text in Wagtail admin -> Settings -> System settings.</p>', @@ -245,6 +263,10 @@ class SystemMessagesSettings(BaseSetting): ) panels = [ + MultiFieldPanel([ + FieldPanel('site_logo_default'), + FieldPanel('site_logo_mobile'), + ], 'Site logo'), FieldPanel('footer_content'), MultiFieldPanel([ FieldPanel('title_404'), diff --git a/opentech/settings/local.py.example b/opentech/settings/local.py.example index af1a71bdd97806311a13acdae7e54af15a4b5cea..92f433c824e09f4227d49a046a04bc066046165d 100644 --- a/opentech/settings/local.py.example +++ b/opentech/settings/local.py.example @@ -1,6 +1,9 @@ CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', + }, + "wagtailcache": { + 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } } diff --git a/opentech/settings/production.py b/opentech/settings/production.py index baa850f69e7dc8f20d36faa8cb5f96a979d2c4f1..d73af0fdaedc715b92b116b2d556e4da1e1c3e37 100644 --- a/opentech/settings/production.py +++ b/opentech/settings/production.py @@ -1,20 +1,19 @@ import os -import django_heroku -import sentry_sdk - -from sentry_sdk.integrations.django import DjangoIntegration - from .base import * # noqa # Disable debug mode DEBUG = False # Configuration from environment variables -# Alternatively, you can set these in a local.py file on the server - env = os.environ.copy() +# Alternatively, you can set these in a local.py file on the server +try: + from .local import * # noqa +except ImportError: + pass + # Mailgun configuration. if 'MAILGUN_API_KEY' in env: EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend' @@ -26,6 +25,8 @@ if 'MAILGUN_API_KEY' in env: # Sentry configuration. if 'SENTRY_DSN' in env: + import sentry_sdk + from sentry_sdk.integrations.django import DjangoIntegration sentry_sdk.init( dsn=env['SENTRY_DSN'], environment=env.get('SENTRY_ENVIRONMENT', None), @@ -33,4 +34,7 @@ if 'SENTRY_DSN' in env: ) # Heroku configuration. -django_heroku.settings(locals()) +# Set ON_HEROKU to true in Config Vars or via cli "heroku config:set ON_HEROKU=true". +if 'ON_HEROKU' in env: + import django_heroku + django_heroku.settings(locals()) diff --git a/opentech/templates/base.html b/opentech/templates/base.html index 57c690e3b1e538d441c6e2f47b890764d5fd4d11..98d31618532cf10368970a059ab96975bcf14b22 100644 --- a/opentech/templates/base.html +++ b/opentech/templates/base.html @@ -83,9 +83,20 @@ <div class="header__inner wrapper wrapper--large"> <a href="{% slugurl 'home' %}" aria-label="Home link"> + {% if settings.utils.SystemMessagesSettings.site_logo_default %} + {% image settings.utils.SystemMessagesSettings.site_logo_default width-215 as logo_default %} + <img class="header__logo header__logo--desktop" src="{{ logo_default.url }}"> + {% if settings.utils.SystemMessagesSettings.site_logo_mobile %} + {% image settings.utils.SystemMessagesSettings.site_logo_mobile width-60 as logo_mobile %} + <img class="header__logo header__logo--mobile" src="{{ logo_mobile.url }}"> + {% else %} + <img class="header__logo header__logo--mobile" src="{{ logo_default.url }}"> + {% endif %} + {% else %} <svg class="header__logo header__logo--desktop header__logo--desktop-light"><use xlink:href="#logo-desktop"></use></svg> <svg class="header__logo header__logo--desktop header__logo--desktop-dark"><use xlink:href="#logo-desktop--dark"></use></svg> <svg class="header__logo header__logo--mobile"><use xlink:href="#logo-mobile"></use></svg> + {% endif %} </a> <div class="header__inner header__inner--mobile-buttons"> @@ -112,7 +123,12 @@ <section class="header__menus header__menus--mobile"> <div class="header__inner header__inner--menu-open"> <a href="{% slugurl 'home' %}" aria-label="Home link"> + {% if settings.utils.SystemMessagesSettings.site_logo_mobile %} + {% image settings.utils.SystemMessagesSettings.site_logo_mobile width-60 as logo_mobile %} + <img class="header__logo header__logo--mobile" src="{{ logo_mobile.url }}"> + {% else %} <svg class="header__logo header__logo--mobile"><use xlink:href="#logo-mobile"></use></svg> + {% endif %} </a> <div class="header__inner header__inner--mobile-buttons"> <button class="button js-mobile-search-toggle" aria-haspopup="true" aria-label="Toggle mobile search"> diff --git a/requirements.txt b/requirements.txt index c12bfb40c8c07c9370eba7cfff447fb487d3f58a..86a1a0ca06c7aa2862217993dca879217e643af2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ scout-apm==2.5.0 sentry-sdk==0.12.3 # Production dependencies -boto3==1.9.160 +boto3==1.9.245 celery==4.2.1 dj-database-url==0.5.0 django-anymail==6.0.1 @@ -40,7 +40,7 @@ djangorestframework==3.9.2 djangorestframework-api-key==1.3.0 django==2.1.11 gunicorn==19.9.0 -mailchimp3==3.0.7 +mailchimp3==3.0.9 mistune==0.8.4 more-itertools==7.2.0 Pillow==5.4.1