diff --git a/opentech/apply/dashboard/templates/dashboard/dashboard.html b/opentech/apply/dashboard/templates/dashboard/dashboard.html index d467da86e95e7e625d96e9ac200dd665ce101eff..73fead516f52b250612d4e7706efe9dee2e00f93 100644 --- a/opentech/apply/dashboard/templates/dashboard/dashboard.html +++ b/opentech/apply/dashboard/templates/dashboard/dashboard.html @@ -5,10 +5,13 @@ {% block content %} <div class="admin-bar"> - <div class="admin-bar__inner"> + <div class="admin-bar__inner admin-bar__inner--with-button"> {% block page_header %} <h1 class="gamma heading heading--no-margin heading--bold">Dashboard</h1> {% endblock %} + <a href="{% url 'wagtailadmin_home' %}" class="button button--primary {{ class }}}"> + Apply admin + </a> </div> </div> <div class="wrapper wrapper--large wrapper--inner-space-medium"> diff --git a/opentech/apply/dashboard/wagtail_hooks.py b/opentech/apply/dashboard/wagtail_hooks.py new file mode 100644 index 0000000000000000000000000000000000000000..89843e82ecd19e03272655582f5599b014b02753 --- /dev/null +++ b/opentech/apply/dashboard/wagtail_hooks.py @@ -0,0 +1,19 @@ +from urllib.parse import urljoin + +from django.urls import reverse + +from wagtail.core import hooks +from wagtail.admin.menu import MenuItem + +from opentech.apply.home.models import ApplyHomePage + + +@hooks.register('register_admin_menu_item') +def register_dashboard_menu_item(): + apply_home = ApplyHomePage.objects.first() + return MenuItem( + 'Apply Dashboard', + urljoin(apply_home.url, reverse('dashboard:dashboard', 'opentech.apply.urls')), + classnames='icon icon-arrow-left', + order=100000, + ) diff --git a/opentech/apply/determinations/tests/test_views.py b/opentech/apply/determinations/tests/test_views.py index 5f3f0c00523e6d466094e84bd86924ac5d4e2af2..b5d8f49823dc05300e77ebbc06123f79a183ff9d 100644 --- a/opentech/apply/determinations/tests/test_views.py +++ b/opentech/apply/determinations/tests/test_views.py @@ -1,42 +1,18 @@ -from django.test import TestCase, RequestFactory from django.urls import reverse from opentech.apply.activity.models import Activity from opentech.apply.determinations.models import ACCEPTED from opentech.apply.users.tests.factories import StaffFactory, UserFactory from opentech.apply.funds.tests.factories import ApplicationSubmissionFactory +from opentech.apply.utils.testing import BaseViewTestCase from .factories import DeterminationFactory -class BaseTestCase(TestCase): - url_name = '' - user_factory = None - - def setUp(self): - self.factory = RequestFactory() - self.user = self.user_factory() - self.client.force_login(self.user) - - def url(self, instance, view_name='detail'): - full_url_name = self.url_name.format(view_name) - url = reverse(full_url_name, kwargs=self.get_kwargs(instance)) - request = self.factory.get(url, secure=True) - return request.build_absolute_uri() - - def get_page(self, instance, view_name='detail'): - return self.client.get(self.url(instance, view_name), secure=True, follow=True) - - def post_page(self, instance, data, view_name='detail'): - return self.client.post(self.url(instance, view_name), data, secure=True, follow=True) - - def refresh(self, instance): - return instance.__class__.objects.get(id=instance.id) - - -class StaffDeterminationsTestCase(BaseTestCase): +class StaffDeterminationsTestCase(BaseViewTestCase): user_factory = StaffFactory url_name = 'funds:submissions:determinations:{}' + base_view_name = 'detail' def get_kwargs(self, instance): return {'submission_pk': instance.submission.id} @@ -60,9 +36,10 @@ class StaffDeterminationsTestCase(BaseTestCase): self.assertTrue(response.context['can_view_extended_data']) -class DeterminationFormTestCase(BaseTestCase): +class DeterminationFormTestCase(BaseViewTestCase): user_factory = StaffFactory url_name = 'funds:submissions:determinations:{}' + base_view_name = 'detail' def get_kwargs(self, instance): return {'submission_pk': instance.id} @@ -141,9 +118,10 @@ class DeterminationFormTestCase(BaseTestCase): self.assertEqual(submission_next.status, 'draft_proposal') -class UserDeterminationFormTestCase(BaseTestCase): +class UserDeterminationFormTestCase(BaseViewTestCase): user_factory = UserFactory url_name = 'funds:submissions:determinations:{}' + base_view_name = 'detail' def get_kwargs(self, instance): return {'submission_pk': instance.id} diff --git a/opentech/apply/funds/templates/funds/includes/field.html b/opentech/apply/funds/templates/funds/includes/field.html index 51c02144e836570e2240c8a97d35aff4450a9cdb..b4e71892f1d7cc7f63fccb07dd72363ff7dc6c49 100644 --- a/opentech/apply/funds/templates/funds/includes/field.html +++ b/opentech/apply/funds/templates/funds/includes/field.html @@ -21,7 +21,7 @@ {% endif %} {% if field.help_text %} - <p class="form__help">{{ field.help_text }}</p> + <p class="form__help">{{ field.help_text|safe }}</p> {% endif %} <div class="form__item"> diff --git a/opentech/apply/funds/tests/factories/models.py b/opentech/apply/funds/tests/factories/models.py index 05e972613c8e43636b4cfad614a334589f68c2e9..286c340cd1fd4a05721107d15625c0a5aa5ec4cc 100644 --- a/opentech/apply/funds/tests/factories/models.py +++ b/opentech/apply/funds/tests/factories/models.py @@ -21,6 +21,7 @@ from opentech.apply.funds.models.forms import ( ) from opentech.apply.users.tests.factories import StaffFactory, UserFactory from opentech.apply.stream_forms.testing.factories import FormDataFactory +from opentech.apply.home.factories import ApplyHomePageFactory from . import blocks @@ -35,6 +36,7 @@ __all__ = [ 'RoundBaseFormFactory', 'LabFactory', 'LabBaseFormFactory', + 'LabSubmissionFactory', 'SealedRoundFactory', 'SealedSubmissionFactory', ] @@ -69,6 +71,21 @@ class FundTypeFactory(wagtail_factories.PageFactory): # Will need to update how the stages are identified as Fund Page changes workflow_name = factory.LazyAttribute(lambda o: list(FundType.WORKFLOW_CHOICES.keys())[o.workflow_stages - 1]) + @factory.post_generation + def parent(self, create, extracted_parent, **parent_kwargs): + # THIS MUST BE THE FIRST POST GENERATION METHOD OR THE OBJECT WILL BE UNSAVED + if create: + if extracted_parent and parent_kwargs: + raise ValueError('Cant pass a parent instance and attributes') + + if not extracted_parent: + parent = ApplyHomePageFactory(**parent_kwargs) + else: + # Assume root node if no parent passed + parent = extracted_parent + + parent.add_child(instance=self) + @factory.post_generation def forms(self, create, extracted, **kwargs): if create: @@ -98,7 +115,7 @@ class AbstractRelatedFormFactory(factory.DjangoModelFactory): class ApplicationBaseFormFactory(AbstractRelatedFormFactory): class Meta: model = ApplicationBaseForm - application = factory.SubFactory(FundTypeFactory, parent=None) + application = factory.SubFactory(FundTypeFactory) class ApplicationFormFactory(factory.DjangoModelFactory): @@ -120,7 +137,7 @@ class RoundFactory(wagtail_factories.PageFactory): ) title = factory.Sequence('Round {}'.format) - start_date = factory.Sequence(lambda n: datetime.date.today() + datetime.timedelta(days=7 * n)) + start_date = factory.Sequence(lambda n: datetime.date.today() + datetime.timedelta(days=7 * n + 1)) end_date = factory.Sequence(lambda n: datetime.date.today() + datetime.timedelta(days=7 * (n + 1))) lead = factory.SubFactory(StaffFactory) @@ -149,7 +166,7 @@ class TodayRoundFactory(RoundFactory): class RoundBaseFormFactory(AbstractRelatedFormFactory): class Meta: model = RoundBaseForm - round = factory.SubFactory(RoundFactory, parent=None) + round = factory.SubFactory(RoundFactory) class LabFactory(wagtail_factories.PageFactory): @@ -179,7 +196,7 @@ class LabFactory(wagtail_factories.PageFactory): class LabBaseFormFactory(AbstractRelatedFormFactory): class Meta: model = LabBaseForm - lab = factory.SubFactory(LabFactory, parent=None) + lab = factory.SubFactory(LabFactory) class ApplicationFormDataFactory(FormDataFactory): @@ -235,6 +252,11 @@ class SealedSubmissionFactory(ApplicationSubmissionFactory): ) +class LabSubmissionFactory(ApplicationSubmissionFactory): + round = None + page = factory.SubFactory(LabFactory) + + class ApplicationRevisionFactory(factory.DjangoModelFactory): class Meta: model = ApplicationRevision diff --git a/opentech/apply/funds/tests/test_models.py b/opentech/apply/funds/tests/test_models.py index 4570f9a12ded04f3def74fa8c46884fe8ad440d4..44b07e3891733bd7d965a3a58d94d785edb59433 100644 --- a/opentech/apply/funds/tests/test_models.py +++ b/opentech/apply/funds/tests/test_models.py @@ -179,6 +179,7 @@ class TestRoundModelWorkflowAndForms(TestCase): self.assertNotEqual(round_form, fund_form) +@override_settings(ROOT_URLCONF='opentech.apply.urls') class TestFormSubmission(TestCase): def setUp(self): self.site = Site.objects.first() diff --git a/opentech/apply/funds/tests/test_views.py b/opentech/apply/funds/tests/test_views.py index 5e31117a1b44c6bf70e03c547c4489d61f703fdb..ebacea7f3a05e1f73ab122cf40782c50fc7ca73d 100644 --- a/opentech/apply/funds/tests/test_views.py +++ b/opentech/apply/funds/tests/test_views.py @@ -4,6 +4,7 @@ from opentech.apply.activity.models import Activity from opentech.apply.funds.tests.factories import ( ApplicationSubmissionFactory, ApplicationRevisionFactory, + LabSubmissionFactory, SealedRoundFactory, SealedSubmissionFactory, ) @@ -29,6 +30,11 @@ class TestStaffSubmissionView(BaseSubmissionViewTestCase): response = self.get_page(submission) self.assertContains(response, submission.title) + def test_can_view_a_lab_submission(self): + submission = LabSubmissionFactory() + response = self.get_page(submission) + self.assertContains(response, submission.title) + def test_can_progress_phase(self): submission = ApplicationSubmissionFactory() next_status = list(submission.get_actions_for_user(self.user))[0][0] diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py index 40f0870ec2e63e098cf50bccee193ee69322aba7..6d842387e7ecf3c03cdf546ac40f23582062831c 100644 --- a/opentech/apply/funds/views.py +++ b/opentech/apply/funds/views.py @@ -208,7 +208,11 @@ class SubmissionSealedView(DetailView): @classmethod def round_is_sealed(cls, submission): - return submission.round.specific.is_sealed + try: + return submission.round.specific.is_sealed + except AttributeError: + # Its a lab - cant be sealed + return False @classmethod def has_peeked(cls, request, submission): diff --git a/opentech/apply/home/factories.py b/opentech/apply/home/factories.py new file mode 100644 index 0000000000000000000000000000000000000000..aa53b814af17d25b41e4a9a896fa12f3892c0df5 --- /dev/null +++ b/opentech/apply/home/factories.py @@ -0,0 +1,14 @@ +import factory +import wagtail_factories + +from .models import ApplyHomePage + + +class ApplyHomePageFactory(wagtail_factories.PageFactory): + class Meta: + model = ApplyHomePage + + @factory.post_generation + def site(self, create, extracted_site, **site_kwargs): + if create: + wagtail_factories.SiteFactory(root_page=self, is_default_site=True) diff --git a/opentech/apply/middleware.py b/opentech/apply/middleware.py new file mode 100644 index 0000000000000000000000000000000000000000..80daac1236b8eef0b846128f0aa4689609070c63 --- /dev/null +++ b/opentech/apply/middleware.py @@ -0,0 +1,17 @@ +from .home.models import ApplyHomePage + + +def apply_url_conf_middleware(get_response): + # If we are on a page which belongs to the same site as an ApplyHomePage + # we change the url conf to one that includes links to all the logged + # in functionality. Login and Logout are included with the global package + # of urls + def middleware(request): + homepage = request.site.root_page.specific + if isinstance(homepage, ApplyHomePage): + request.urlconf = 'opentech.apply.urls' + + response = get_response(request) + return response + + return middleware diff --git a/opentech/apply/urls.py b/opentech/apply/urls.py index e730c8025c66f734737c7d20369e1cdfbd58fff1..27483633b8fb3bad18a4649f396e9a4a7758cc62 100644 --- a/opentech/apply/urls.py +++ b/opentech/apply/urls.py @@ -3,11 +3,15 @@ from django.urls import include, path from .users import urls as users_urls from .dashboard import urls as dashboard_urls +from opentech.urls import urlpatterns as base_urlpatterns + urlpatterns = [ path('apply/', include('opentech.apply.funds.urls', 'apply')), path('activity/', include('opentech.apply.activity.urls', 'activity')), - path('account/', include(users_urls)), + path('', include(users_urls)), path('dashboard/', include(dashboard_urls)), path('hijack/', include('hijack.urls', 'hijack')), ] + +urlpatterns += base_urlpatterns diff --git a/opentech/apply/users/forms.py b/opentech/apply/users/forms.py index b27eab0b8f4091fe4368787ac298499078a473fd..8338010a6bce407dfa03be69e97d03b05f28972e 100644 --- a/opentech/apply/users/forms.py +++ b/opentech/apply/users/forms.py @@ -1,5 +1,6 @@ from django import forms from django.contrib.auth import get_user_model +from django.utils.translation import gettext_lazy as _ from wagtail.users.forms import UserEditForm, UserCreationForm @@ -36,7 +37,10 @@ class ProfileForm(forms.ModelForm): if not self.instance.has_usable_password(): # User is registered with oauth - no password change allowed - del self.fields['email'] + email_field = self.fields['email'] + email_field.disabled = True + email_field.required = False + email_field.help_text = _('You are registered using OAuth, please contact an admin if you need to change your email address.') def clean_slack(self): slack = self.cleaned_data['slack'] diff --git a/opentech/apply/users/groups.py b/opentech/apply/users/groups.py index e3b215e3d4f12c57380e9a7cc8e01590b652db70..42a49daa26f41ef5674bb083df6a342098fb35d2 100644 --- a/opentech/apply/users/groups.py +++ b/opentech/apply/users/groups.py @@ -2,48 +2,12 @@ STAFF_GROUP_NAME = 'Staff' REVIEWER_GROUP_NAME = 'Reviewer' GROUPS = [ - { - 'name': 'Applicant', - 'permissions': [], - }, { 'name': REVIEWER_GROUP_NAME, 'permissions': [], }, - { - 'name': 'Advisor', - 'permissions': [], - }, { 'name': STAFF_GROUP_NAME, 'permissions': [], }, - { - 'name': 'Manager', - 'permissions': [ - 'add_image', - 'change_image', - 'delete_image', - 'add_document', - 'change_document', - 'delete_document', - 'access_admin', - ], - }, - { - 'name': 'Administrator', - 'permissions': [ - 'add_image', - 'change_image', - 'delete_image', - 'add_document', - 'change_document', - 'delete_document', - 'add_user', - 'change_user', - 'delete_user', - 'access_admin', - 'change_site', - ], - } ] diff --git a/opentech/apply/users/management/commands/migrate_users.py b/opentech/apply/users/management/commands/migrate_users.py index 7d8ef0b505ce603d844db65441b76ba9263d3a83..c101c1c9454622970c9a4e96c82011bc9b63ac52 100644 --- a/opentech/apply/users/management/commands/migrate_users.py +++ b/opentech/apply/users/management/commands/migrate_users.py @@ -61,9 +61,8 @@ class Command(BaseCommand): def get_user_groups(self, user): groups = [] role_map = { - 'proposer': 'Applicant', - 'council': 'Advisor', - 'administrator': 'Administrator', + 'council': 'Reviewer', + 'administrator': 'Editors', 'dev': 'Administrator', } diff --git a/opentech/apply/users/templates/users/activation/email.txt b/opentech/apply/users/templates/users/activation/email.txt index 864c367647c5ebf060236c606c838da44457c194..a32a55ca6e1e1b1a04c9f40eaf239965f4a6c7c7 100644 --- a/opentech/apply/users/templates/users/activation/email.txt +++ b/opentech/apply/users/templates/users/activation/email.txt @@ -4,7 +4,7 @@ Dear {{ name|default:username }}, An account on Open Technology Fund has been created. Activate your account by clicking this link or copying and pasting it to your browser: -{% if site %}{{ site.root_url }}{% else %}{{ base_url }}{% endif %}{% url 'users:activate' uidb64=uid token=token %} +{% if site %}{{ site.root_url }}{% else %}{{ base_url }}{% endif %}{{ activation_path }} This link can be used once to log in and will lead you to a page where you can set your password. diff --git a/opentech/apply/users/templates/users/activation/email_subject.txt b/opentech/apply/users/templates/users/activation/email_subject.txt deleted file mode 100644 index 1f1a8daafc3057f4227badf39bc239a6c131b73a..0000000000000000000000000000000000000000 --- a/opentech/apply/users/templates/users/activation/email_subject.txt +++ /dev/null @@ -1 +0,0 @@ -Account details for {{ username }} at Open Technology Fund diff --git a/opentech/apply/users/templates/users/password_reset/complete.html b/opentech/apply/users/templates/users/password_reset/complete.html index 00740fd926d8ee3539dabbe12f556a5bfd0b9a84..fb4f06aa4280585f99ae7d1d606bc68b4ce26567 100644 --- a/opentech/apply/users/templates/users/password_reset/complete.html +++ b/opentech/apply/users/templates/users/password_reset/complete.html @@ -4,6 +4,6 @@ {% block title %}{% trans "Reset password" %}{% endblock %} {% block content %} <h1>{% trans "Password change successful" %}</h1> <p> - <a href="{% url 'users:login' %}">{% trans "Log in" %}</a> + <a href="{% url 'users_public:login' %}">{% trans "Log in" %}</a> </p> {% endblock %} diff --git a/opentech/apply/users/tests/test_forms.py b/opentech/apply/users/tests/test_forms.py index 06e81fb933f048101100af1a60e7dff680c4f9e8..edb991775afdc6b395f9660b098181322fe9914e 100644 --- a/opentech/apply/users/tests/test_forms.py +++ b/opentech/apply/users/tests/test_forms.py @@ -48,10 +48,9 @@ class TestStaffProfileForm(BaseTestProfileForm): def setUp(self): self.staff = StaffFactory() - def test_cant_change_password(self): + def test_cant_change_email(self): new_email = 'me@this.com' - form = self.submit_form(self.staff, email=new_email) - self.assertFalse('email' in form.fields) + self.submit_form(self.staff, email=new_email) self.staff.refresh_from_db() self.assertNotEqual(new_email, self.staff.email) diff --git a/opentech/apply/users/tests/test_oauth_access.py b/opentech/apply/users/tests/test_oauth_access.py index 92d3f2ae25fa42123fb5d1530574e3604a1c2d72..1cc2f13ab373fe94f72bb9e3b4d09a753bba97e8 100644 --- a/opentech/apply/users/tests/test_oauth_access.py +++ b/opentech/apply/users/tests/test_oauth_access.py @@ -4,6 +4,7 @@ from django.test import TestCase, override_settings from django.urls import reverse +@override_settings(ROOT_URLCONF='opentech.apply.urls') class TestOAuthAccess(TestCase): def login(self): email = 'test@email.com' @@ -19,8 +20,12 @@ class TestOAuthAccess(TestCase): """ oauth_page = reverse('users:oauth') response = self.client.get(oauth_page, follow=True) - self.assertRedirects(response, reverse( - 'users:login') + '?next=' + reverse('users:oauth'), status_code=301, target_status_code=200) + self.assertRedirects( + response, + reverse('users_public:login') + '?next=' + reverse('users:oauth'), + status_code=301, + target_status_code=200, + ) @override_settings() def test_oauth_not_set_up(self): diff --git a/opentech/apply/users/tests/test_utils.py b/opentech/apply/users/tests/test_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..8d9c9fa6c4a1dd7a2c167b5bb91ec35f95719045 --- /dev/null +++ b/opentech/apply/users/tests/test_utils.py @@ -0,0 +1,12 @@ +from django.core import mail +from django.test import TestCase + +from opentech.apply.users.tests.factories import UserFactory + +from ..utils import send_activation_email + + +class TestActivationEmail(TestCase): + def test_activation_email_includes_link(self): + send_activation_email(UserFactory()) + self.assertEqual(len(mail.outbox), 1) diff --git a/opentech/apply/users/tests/test_views.py b/opentech/apply/users/tests/test_views.py index 1943e508315cfd891b11d2095dd1b5e2639c4af0..e5c2a6dc4320916fe508dae4980bcf12789e1263 100644 --- a/opentech/apply/users/tests/test_views.py +++ b/opentech/apply/users/tests/test_views.py @@ -1,9 +1,10 @@ -from django.test import TestCase +from django.test import override_settings, TestCase from django.urls import reverse from .factories import OAuthUserFactory, StaffFactory, UserFactory +@override_settings(ROOT_URLCONF='opentech.apply.urls') class BaseTestProfielView(TestCase): @classmethod def setUpTestData(cls): @@ -19,7 +20,7 @@ class TestProfileView(BaseTestProfielView): self.client.logout() response = self.client.get(self.url, follow=True) # Initial redirect will be via to https through a 301 - self.assertRedirects(response, reverse('users:login') + '?next=' + self.url, status_code=301) + self.assertRedirects(response, reverse('users_public:login') + '?next=' + self.url, status_code=301) def test_includes_change_password(self): response = self.client.get(self.url, follow=True) @@ -43,7 +44,3 @@ class TestStaffProfileView(BaseTestProfielView): def test_can_set_slack_name(self): response = self.client.get(self.url, follow=True) self.assertContains(response, 'Slack name') - - def test_can_not_set_email(self): - response = self.client.get(self.url, follow=True) - self.assertNotContains(response, 'Email') diff --git a/opentech/apply/users/urls.py b/opentech/apply/users/urls.py index ddb5fb2634500e85f9f4382dba4354aabbaf32ef..02e7899ca76bf89522a1b0f4d7c153033b404fcf 100644 --- a/opentech/apply/users/urls.py +++ b/opentech/apply/users/urls.py @@ -1,4 +1,4 @@ -from django.urls import path +from django.urls import path, include from django.contrib.auth import views as auth_views from django.urls import reverse_lazy @@ -7,9 +7,8 @@ from opentech.apply.users.views import AccountView, become, oauth, ActivationVie app_name = 'users' -urlpatterns = [ - path('', AccountView.as_view(), name='account'), - path('become/', become, name='become'), + +public_urlpatterns = [ path( 'login/', auth_views.LoginView.as_view( @@ -21,52 +20,58 @@ urlpatterns = [ # Log out path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'), +] - # Password change - path( - 'password/', - auth_views.PasswordChangeView.as_view( - template_name="users/change_password.html", - success_url=reverse_lazy('users:account') - ), - name='password_change', - ), - # Password reset - path( - 'reset/', - auth_views.PasswordResetView.as_view( - template_name='users/password_reset/form.html', - email_template_name='users/password_reset/email.txt', - success_url=reverse_lazy('users:password_reset_done') - ), - name='password_reset', - ), - path( - 'reset/done/', - auth_views.PasswordResetDoneView.as_view(template_name='users/password_reset/done.html'), - name='password_reset_done' - ), - path( - 'reset/confirm/<uidb64>/<token>/', - auth_views.PasswordResetConfirmView.as_view( - template_name='users/password_reset/confirm.html', - post_reset_login=True, - post_reset_login_backend='django.contrib.auth.backends.ModelBackend', - success_url=reverse_lazy('users:account') +urlpatterns = [ + path('account/', include([ + path('', AccountView.as_view(), name='account'), + path('become/', become, name='become'), + path('password/', include([ + path( + 'change/', + auth_views.PasswordChangeView.as_view( + template_name="users/change_password.html", + success_url=reverse_lazy('users:account') + ), + name='password_change', + ), + path( + 'reset/', + auth_views.PasswordResetView.as_view( + template_name='users/password_reset/form.html', + email_template_name='users/password_reset/email.txt', + success_url=reverse_lazy('users:password_reset_done') + ), + name='password_reset', + ), + path( + 'reset/done/', + auth_views.PasswordResetDoneView.as_view(template_name='users/password_reset/done.html'), + name='password_reset_done' + ), + path( + 'reset/confirm/<uidb64>/<token>/', + auth_views.PasswordResetConfirmView.as_view( + template_name='users/password_reset/confirm.html', + post_reset_login=True, + post_reset_login_backend='django.contrib.auth.backends.ModelBackend', + success_url=reverse_lazy('users:account') + ), + name='password_reset_confirm' + ), + path( + 'reset/complete/', + auth_views.PasswordResetCompleteView.as_view(template_name='users/password_reset/complete.html'), + name='password_reset_complete' + ), + ])), + path( + 'activate/<uidb64>/<token>/', + ActivationView.as_view(), + name='activate' ), - name='password_reset_confirm' - ), - path( - 'reset/complete/', - auth_views.PasswordResetCompleteView.as_view(template_name='users/password_reset/complete.html'), - name='password_reset_complete' - ), - path( - 'activate/<uidb64>/<token>/', - ActivationView.as_view(), - name='activate' - ), - path('activate/password/', create_password, name="activate_password"), - path('oauth', oauth, name='oauth'), + path('activate/', create_password, name="activate_password"), + path('oauth', oauth, name='oauth'), + ])) ] diff --git a/opentech/apply/users/utils.py b/opentech/apply/users/utils.py index 4c40cb43deba360d4a6706f08390a3635a4c78ff..7539e137f579da23f11c50150e0c0dc1fdf9676a 100644 --- a/opentech/apply/users/utils.py +++ b/opentech/apply/users/utils.py @@ -3,6 +3,7 @@ from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.template.loader import render_to_string from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode +from django.urls import reverse def can_use_oauth_check(user): @@ -20,23 +21,29 @@ def can_use_oauth_check(user): return False -def send_activation_email(user, site): +def send_activation_email(user, site=None): """ Send the activation email. The activation key is the username, signed using TimestampSigner. """ token_generator = PasswordResetTokenGenerator() + token = token_generator.make_token(user) + + uid = urlsafe_base64_encode(force_bytes(user.pk)).decode() + + activation_path = reverse('users:activate', kwargs={'uidb64': uid, 'token': token}) + context = { 'user': user, 'name': user.get_full_name(), 'username': user.get_username(), - 'uid': urlsafe_base64_encode(force_bytes(user.pk)), - 'token': token_generator.make_token(user), + 'activation_path': activation_path, } + if site: context.update(site=site) - subject = render_to_string('users/activation/email_subject.txt', context) + subject = 'Account details for {username} at Open Technology Fund'.format(**context) # Force subject to a single line to avoid header-injection issues. subject = ''.join(subject.splitlines()) message = render_to_string('users/activation/email.txt', context) diff --git a/opentech/apply/users/views.py b/opentech/apply/users/views.py index 8f8e3dab59ca43446f461283062957c5c640f132..7dadd850d5bac0ad6c823836d608641d699e36a5 100644 --- a/opentech/apply/users/views.py +++ b/opentech/apply/users/views.py @@ -49,7 +49,7 @@ class AccountView(UpdateView): ) -@login_required(login_url=reverse_lazy('users:login')) +@login_required() def become(request): if request.POST: id = request.POST['user'] @@ -57,7 +57,7 @@ def become(request): return redirect('users:account') -@login_required(login_url=reverse_lazy('users:login')) +@login_required() @require_oauth_whitelist def oauth(request): """Generic, empty view for the OAuth associations.""" diff --git a/opentech/apply/utils/testing/tests.py b/opentech/apply/utils/testing/tests.py index 624a0e774709a6132effa56141744a85f062d82c..c0b0edd1cfd75239c459eedb1d0ae8c0911950d6 100644 --- a/opentech/apply/utils/testing/tests.py +++ b/opentech/apply/utils/testing/tests.py @@ -1,6 +1,6 @@ from django.contrib.messages.storage.fallback import FallbackStorage from django.contrib.auth.models import AnonymousUser -from django.test import TestCase, RequestFactory +from django.test import override_settings, TestCase, RequestFactory from django.urls import reverse @@ -17,6 +17,7 @@ def make_request(user=AnonymousUser(), data={}, method='get', site=None): return request +@override_settings(ROOT_URLCONF='opentech.apply.urls') class BaseViewTestCase(TestCase): url_name = '' # resolvable url, you should use "path:to:view:{}" and {} with be replaced with base_view_name base_view_name = '' diff --git a/opentech/public/home/templates/home/home_page.html b/opentech/public/home/templates/home/home_page.html index 1b66674fdb85860b3672e7e00fae02eb029dc349..de9864f1f1d4734b35cacf95829d39181a5b9215 100644 --- a/opentech/public/home/templates/home/home_page.html +++ b/opentech/public/home/templates/home/home_page.html @@ -52,10 +52,7 @@ </section> <div class="header__button-container"> - <a href="{% url 'users:login' %}" class="button button--transparent button--contains-icons"> - <svg class="icon icon--person"><use xlink:href="#person-icon"></use></svg> - My OTF - </a> + {% include "utils/includes/login_button.html" %} <div class="button button--google-translate" id="google_translate_element"></div> </div> </div> diff --git a/opentech/public/mailchimp/__init__.py b/opentech/public/mailchimp/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/opentech/public/mailchimp/apps.py b/opentech/public/mailchimp/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..6825eca974a4a4ece3a2fb3acbc7ab9ae8799f9d --- /dev/null +++ b/opentech/public/mailchimp/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class MailchimpConfig(AppConfig): + name = 'mailchimp' diff --git a/opentech/public/mailchimp/forms.py b/opentech/public/mailchimp/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..afda5b9e3f0c0d79da9646a23b3f7c7de40ce82f --- /dev/null +++ b/opentech/public/mailchimp/forms.py @@ -0,0 +1,15 @@ +from django import forms + + +class NewsletterForm(forms.Form): + email = forms.EmailField(label='Email Address') + fname = forms.CharField(label='First Name', required=False) + lname = forms.CharField(label='Last Name', required=False) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for field in self.fields.values(): + class_name = 'input--secondary' + if field.required: + class_name += ' input__secondary--required' + field.widget.attrs = {'class': class_name} diff --git a/opentech/public/mailchimp/migrations/__init__.py b/opentech/public/mailchimp/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/opentech/public/mailchimp/models.py b/opentech/public/mailchimp/models.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html b/opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html new file mode 100644 index 0000000000000000000000000000000000000000..4c70ae9abe8f9e980d69af7188e9d800df553056 --- /dev/null +++ b/opentech/public/mailchimp/templates/mailchimp/newsletter_signup.html @@ -0,0 +1,16 @@ +<h4>Get the latest internet freedom news</h4> +<form class="form" action="{% url "newsletter:subscribe" %}" method="post"> + <div> + {% for field in newsletter_form %} + <label for="{{ field.id_for_label }}"{% if field.field.required %} required{% endif %}> + <span>{{ field.label }}</span> + {% if field.field.required %} + <span class="form__required">*</span> + {% endif %} + {{ field }} + {% endfor %} + <div class="form-actions form-wrapper"> + <input type="submit" value="Sign up" class="form-submit link link--button-transparent link--footer-signup"> + </div> + </div> +</form> diff --git a/opentech/public/mailchimp/tests.py b/opentech/public/mailchimp/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..4e838712116aa9e1e3ef5ebae892e6706f7b1d64 --- /dev/null +++ b/opentech/public/mailchimp/tests.py @@ -0,0 +1,69 @@ +from unittest import mock +import re + +from django.test import override_settings, TestCase +from django.urls import reverse + +import responses + + +any_url = re.compile(".") + + +class TestNewsletterView(TestCase): + url = reverse('newsletter:subscribe') + + def setUp(self): + self.origin = 'https://testserver/' + self.client.defaults = {'HTTP_ORIGIN': self.origin} + + def test_redirected_home_if_get(self): + response = self.client.get(self.url, secure=True, follow=True) + request = response.request + self.assertRedirects(response, '{}://{}/'.format(request['wsgi.url_scheme'], request['SERVER_NAME'])) + + @override_settings( + MAILCHIMP_API_KEY='a' * 32, + MAILCHIMP_LIST_ID='12345' + ) + @responses.activate + def test_can_subscribe(self): + responses.add(responses.POST, any_url, json={'id': '1234'}, status=200) + + response = self.client.post(self.url, data={'email': 'email@email.com'}, secure=True, follow=True) + self.assertRedirects(response, self.origin) + + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertIn('Thank you', str(messages[0])) + + def test_error_in_form(self): + response = self.client.post(self.url, data={'email': 'email_is_bad.com'}, secure=True, follow=True) + self.assertRedirects(response, self.origin) + + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertIn('errors with', str(messages[0])) + + @override_settings( + MAILCHIMP_API_KEY='a' * 32, + MAILCHIMP_LIST_ID='12345' + ) + @responses.activate + @mock.patch('opentech.public.mailchimp.views.logging') + def test_error_with_mailchimp(self, logging): + # Copied from the mailchimp playground + response_data = { + "title": "Invalid Resource", + "status": 400, + "detail": "Please provide a valid email address.", + } + responses.add(responses.POST, any_url, json=response_data, status=400) + response = self.client.post(self.url, data={'email': 'email@email.com'}, secure=True, follow=True) + + self.assertRedirects(response, self.origin) + + messages = list(response.context['messages']) + self.assertEqual(len(messages), 1) + self.assertIn('problem', str(messages[0])) + logging.info.assert_called_once_with(response_data) diff --git a/opentech/public/mailchimp/urls.py b/opentech/public/mailchimp/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..4fe8fd700905dce757a61635859439915c5eb90f --- /dev/null +++ b/opentech/public/mailchimp/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from .views import MailchimpSubscribeView + + +app_name = 'newsletter' + + +urlpatterns = [ + path('subscribe/', MailchimpSubscribeView.as_view(), name='subscribe') +] diff --git a/opentech/public/mailchimp/views.py b/opentech/public/mailchimp/views.py new file mode 100644 index 0000000000000000000000000000000000000000..19a7f6795851dc2bbf029d4035a9e7772b9ee436 --- /dev/null +++ b/opentech/public/mailchimp/views.py @@ -0,0 +1,84 @@ +import logging + +from django.conf import settings +from django.contrib import messages +from django.http import HttpResponseRedirect +from django.utils.decorators import method_decorator +from django.utils.translation import ugettext_lazy as _ +from django.views.decorators.csrf import csrf_exempt +from django.views.generic import RedirectView +from django.views.generic.edit import FormMixin + +from mailchimp3 import MailChimp + +from .forms import NewsletterForm + +logging.getLogger('opentech') + + +@method_decorator(csrf_exempt, name='dispatch') +class MailchimpSubscribeView(FormMixin, RedirectView): + form_class = NewsletterForm + + def post(self, request, *args, **kwargs): + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + + def form_invalid(self, form): + self.error(form) + return HttpResponseRedirect(self.get_success_url()) + + def form_valid(self, form): + mailchimp_enabled = settings.MAILCHIMP_API_KEY and settings.MAILCHIMP_LIST_ID + + dummy_key = 'a' * 32 + + client = MailChimp(mc_api=settings.MAILCHIMP_API_KEY or dummy_key, timeout=5.0, enabled=mailchimp_enabled) + + data = form.cleaned_data.copy() + email = data.pop('email') + data = { + k.upper(): v + for k, v in data.items() + } + try: + client.lists.members.create(settings.MAILCHIMP_LIST_ID, { + 'email_address': email, + 'status': 'pending', + 'merge_fields': data, + }) + except Exception as e: + self.warning(e) + else: + if mailchimp_enabled: + self.success() + else: + self.warning(Exception( + 'Incorrect Mailchimp configuration: API_KEY: {}, LIST_ID: {}'.format( + str(settings.MAILCHIMP_API_KEY), + str(settings.MAILCHIMP_LIST_ID), + ) + )) + + return super().form_valid(form) + + def error(self, form): + messages.error(self.request, _('Sorry, there were errors with your form.') + str(form.errors)) + + def warning(self, e): + messages.warning(self.request, _('Sorry, there has been an problem. Please try again later.')) + logging.info(e.args[0]) + + def success(self): + messages.success(self.request, _('Thank you for subscribing')) + + def get_success_url(self): + # Go back to where you came from + return self.request.META['HTTP_ORIGIN'] + + def get_redirect_url(self): + # We don't know where you came from, go home + return '/' diff --git a/opentech/public/navigation/migrations/0002_remove_unused_navigation_elements.py b/opentech/public/navigation/migrations/0002_remove_unused_navigation_elements.py index 378abc32a7fe7d45476407628b328a0ff6dcaf0d..e3a9d091357f485095340e4bd6c7d8e6647bfa70 100644 --- a/opentech/public/navigation/migrations/0002_remove_unused_navigation_elements.py +++ b/opentech/public/navigation/migrations/0002_remove_unused_navigation_elements.py @@ -1,5 +1,6 @@ # Generated by Django 2.0.2 on 2018-08-09 11:13 + from django.db import migrations diff --git a/opentech/public/navigation/templates/navigation/footerlinks.html b/opentech/public/navigation/templates/navigation/footerlinks.html deleted file mode 100644 index 098dd8df5b83a3baa07ec85ca9d69df1e2a7fc7d..0000000000000000000000000000000000000000 --- a/opentech/public/navigation/templates/navigation/footerlinks.html +++ /dev/null @@ -1,8 +0,0 @@ -{% load wagtailcore_tags %} -<nav role="navigation" aria-label="Tertiary"> - <ul class="nav nav--tertiary"> - {% for link in footerlinks %} - {% include_block link with class="footer" %} - {% endfor %} - </ul> -</nav> diff --git a/opentech/public/navigation/templates/navigation/footernav.html b/opentech/public/navigation/templates/navigation/footernav.html deleted file mode 100644 index 576feaf688b5dd281bece692256ac135a9c7f8bf..0000000000000000000000000000000000000000 --- a/opentech/public/navigation/templates/navigation/footernav.html +++ /dev/null @@ -1,6 +0,0 @@ -{% load wagtailcore_tags %} -<nav class="grid grid--narrow" role="navigation" aria-label="Tertiary"> - {% for footer in footernav %} - {% include_block footer %} - {% endfor %} -</nav> diff --git a/opentech/public/navigation/templates/navigation/primarynav.html b/opentech/public/navigation/templates/navigation/primarynav.html index 4b1ddb5cdb5fe08e408f59e8f7e075e32ddab282..57b587c8af74afbacc7be1782008d5742f5f5639 100644 --- a/opentech/public/navigation/templates/navigation/primarynav.html +++ b/opentech/public/navigation/templates/navigation/primarynav.html @@ -6,8 +6,5 @@ {% endfor %} </ul> </nav> -<a href="{% url 'users:login' %}" class="link link--button-transparent link--mobile-standout"> - <svg class="icon"><use xlink:href="#person-icon"></use></svg> - My OTF -</a> -<a href="#" class="link link--button-secondary link--mobile-standout">Apply</a> +{% include "utils/includes/login_button.html" with class="link--mobile-standout" %} +<a href="{% pageurl APPLY_SITE.root_page %}" class="link link--button-secondary link--mobile-standout">Apply</a> diff --git a/opentech/public/navigation/templates/navigation/secondarynav.html b/opentech/public/navigation/templates/navigation/secondarynav.html deleted file mode 100644 index 3341960e6a5be2e1b89e4176b4b9309ec62f7cfb..0000000000000000000000000000000000000000 --- a/opentech/public/navigation/templates/navigation/secondarynav.html +++ /dev/null @@ -1,8 +0,0 @@ -{% load wagtailcore_tags %} -<nav role="navigation" aria-label="Secondary"> - <ul class="nav nav--secondary" role="menubar"> - {% for link in secondarynav %} - {% include_block link %} - {% endfor %} - </ul> -</nav> diff --git a/opentech/public/navigation/templates/navigation/sidebar.html b/opentech/public/navigation/templates/navigation/sidebar.html deleted file mode 100644 index b322e2c7f2e50a53f3192c9127bd577aa72d2d8a..0000000000000000000000000000000000000000 --- a/opentech/public/navigation/templates/navigation/sidebar.html +++ /dev/null @@ -1,14 +0,0 @@ -{% load wagtailcore_tags %} -{% if children.exists %} - <aside class="sidebar"> - <div class="sidebar__inner"> - <h5>In this section</h5> - <ul> - {% for child in children %} - <li><a href="{% pageurl child %}">{{ child.title }}</a></li> - {% endfor %} - </ul> - </div> - </aside> -{% endif %} - diff --git a/opentech/public/navigation/templatetags/navigation_tags.py b/opentech/public/navigation/templatetags/navigation_tags.py index 4a9cf1cd8ba75eec3cd13ead7fd2e4819126fa88..84ac38eddfe2816b4808fd7602d0449130d05d79 100644 --- a/opentech/public/navigation/templatetags/navigation_tags.py +++ b/opentech/public/navigation/templatetags/navigation_tags.py @@ -11,7 +11,9 @@ register = template.Library() def primarynav(context): request = context['request'] site = context.get('PUBLIC_SITE', request.site) + apply_site = context.get('APPLY_SITE', request.site) return { 'primarynav': NavigationSettings.for_site(site).primary_navigation, 'request': request, + 'APPLY_SITE': apply_site, } diff --git a/opentech/public/urls.py b/opentech/public/urls.py index c24a04b4f01bffc29ac886b18f0a3326b6eebb31..40b2bb96aa3f0b5df11dc0c953999759bd8b36d5 100644 --- a/opentech/public/urls.py +++ b/opentech/public/urls.py @@ -1,7 +1,10 @@ -from django.urls import path +from django.urls import include, path from .search import views as search_views +from .mailchimp import urls as newsletter_urls + urlpatterns = [ path('search/', search_views.search, name='search'), + path('newsletter/', include(newsletter_urls)) ] diff --git a/opentech/public/utils/context_processors.py b/opentech/public/utils/context_processors.py index 2444fdb1b890c218c1189a74039e68a935ecdbd2..149a40181ae1a97390338f32baa92c4c174ed096 100644 --- a/opentech/public/utils/context_processors.py +++ b/opentech/public/utils/context_processors.py @@ -1,9 +1,12 @@ from opentech.apply.home.models import ApplyHomePage from opentech.public.home.models import HomePage +from opentech.public.mailchimp.forms import NewsletterForm + def global_vars(request): return { 'APPLY_SITE': ApplyHomePage.objects.first().get_site(), 'PUBLIC_SITE': HomePage.objects.first().get_site(), + 'newsletter_form': NewsletterForm() } diff --git a/opentech/public/utils/templates/utils/includes/login_button.html b/opentech/public/utils/templates/utils/includes/login_button.html new file mode 100644 index 0000000000000000000000000000000000000000..21b6e461c7c0d39be25fbfeea8fa21ae22a75ea3 --- /dev/null +++ b/opentech/public/utils/templates/utils/includes/login_button.html @@ -0,0 +1,4 @@ +<a href="{{ request.scheme }}://{{ APPLY_SITE }}{% url 'users_public:login' %}" class="button button--transparent button--contains-icons {{ class }}"> + <svg class="icon icon--person"><use xlink:href="#person-icon"></use></svg> + My OTF +</a> diff --git a/opentech/settings/base.py b/opentech/settings/base.py index 3c529f26e9559dfa840a4a8c86db6bd843ea453e..a1c0454510c658577ec5bfacebaccbeb9e5d3b53 100644 --- a/opentech/settings/base.py +++ b/opentech/settings/base.py @@ -28,6 +28,7 @@ INSTALLED_APPS = [ 'opentech.public.funds', 'opentech.public.home', + 'opentech.public.mailchimp', 'opentech.public.navigation', 'opentech.public.news', 'opentech.public.people', @@ -93,6 +94,8 @@ MIDDLEWARE = [ 'wagtail.core.middleware.SiteMiddleware', 'wagtail.contrib.redirects.middleware.RedirectMiddleware', + + 'opentech.apply.middleware.apply_url_conf_middleware', ] ROOT_URLCONF = 'opentech.urls' @@ -216,7 +219,7 @@ WAGTAIL_USER_EDIT_FORM = 'opentech.apply.users.forms.CustomUserEditForm' WAGTAIL_USER_CREATION_FORM = 'opentech.apply.users.forms.CustomUserCreationForm' WAGTAIL_USER_CUSTOM_FIELDS = ['full_name'] -LOGIN_URL = 'users:login' +LOGIN_URL = 'users_public:login' LOGIN_REDIRECT_URL = 'dashboard:dashboard' AUTHENTICATION_BACKENDS = ( @@ -224,6 +227,7 @@ AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', ) + # Logging LOGGING = { @@ -315,7 +319,7 @@ SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = STAFF_EMAIL_DOMAINS SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '' SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '' -SOCIAL_AUTH_LOGIN_ERROR_URL = 'users:login' +SOCIAL_AUTH_LOGIN_ERROR_URL = 'users_public:login' SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL = 'users:account' # For pipelines, see http://python-social-auth.readthedocs.io/en/latest/pipeline.html?highlight=pipelines#authentication-pipeline @@ -369,3 +373,7 @@ if 'REDIS_URL' in env: CELERY_BROKER_URL = env.get('REDIS_URL') else: CELERY_TASK_ALWAYS_EAGER = True + + +MAILCHIMP_API_KEY = env.get('MAILCHIMP_API_KEY') +MAILCHIMP_LIST_ID = env.get('MAILCHIMP_LIST_ID') diff --git a/opentech/static_src/src/sass/public/components/_form.scss b/opentech/static_src/src/sass/public/components/_form.scss index 1d816674003ee53ff23e93da1afc5777c4b4d9d9..d8d2e59d39c59787e54339a453f01abfa86a1a89 100644 --- a/opentech/static_src/src/sass/public/components/_form.scss +++ b/opentech/static_src/src/sass/public/components/_form.scss @@ -155,7 +155,7 @@ input[type='text']:not(.input--secondary), input[type='date'], input[type='time'], - input[type='email'], + input[type='email']:not(.input--secondary), input[type='number'], input[type='password'], input[type='datetime-local'] { diff --git a/opentech/static_src/src/sass/public/components/_messages.scss b/opentech/static_src/src/sass/public/components/_messages.scss new file mode 100644 index 0000000000000000000000000000000000000000..a732b3c02c88324a2409d84b2ed4c7136d6bd1db --- /dev/null +++ b/opentech/static_src/src/sass/public/components/_messages.scss @@ -0,0 +1,44 @@ +.messages { + position: relative; + right: 50%; + left: 50%; + width: 100vw; + margin-right: -50vw; + margin-left: -50vw; + + &__text { + position: relative; + max-height: 1000px; + padding: 15px; + padding-right: 35px; + border: solid 1px; + + &--info , &--success { + background: $color--mint; + border-color: darken($color--mint, 20%); + } + + &--warning, &--error { + font-weight: bold; + color: $color--white; + background: $color--error; + border-color: darken($color--error, 20%); + } + + &--hide { + max-height: 0; + padding-top: 0; + padding-bottom: 0; + border: 0 none; + transition: all $transition; // sass-lint:disable-line no-transition-all + transform-origin: top; + } + } + + &__close { + position: absolute; + top: 15px; + right: 15px; + + } +} diff --git a/opentech/static_src/src/sass/public/layout/_footer.scss b/opentech/static_src/src/sass/public/layout/_footer.scss index a1f5d97e3d93670304c1c4c8bd4994d426d81eac..ac71e029cd5a8da5db2e8bf03f0fd2fd95203b55 100644 --- a/opentech/static_src/src/sass/public/layout/_footer.scss +++ b/opentech/static_src/src/sass/public/layout/_footer.scss @@ -45,16 +45,18 @@ } } - input[type='text'] { - width: 100%; - max-width: 390px; - margin-bottom: 1rem; - color: $color--white; - background: transparent; - border-top: 0; - border-right: 0; - border-bottom: 4px solid $color--light-blue; - border-left: 0; + input{ + &[type='text'], &[type='email'] { + width: 100%; + max-width: 390px; + margin-bottom: 1rem; + color: $color--white; + background: transparent; + border-top: 0; + border-right: 0; + border-bottom: 4px solid $color--light-blue; + border-left: 0; + } } label { diff --git a/opentech/static_src/src/sass/public/main.scss b/opentech/static_src/src/sass/public/main.scss index de867a4a611b89cc7c5a13b4bdca4800c758e580..8928471a2984f625853c0b1af4699162ca4ef311 100755 --- a/opentech/static_src/src/sass/public/main.scss +++ b/opentech/static_src/src/sass/public/main.scss @@ -28,6 +28,7 @@ @import 'components/list'; @import 'components/listing'; @import 'components/media-box'; +@import 'components/messages'; @import 'components/nav'; @import 'components/responsive-object'; @import 'components/rich-text'; @@ -40,4 +41,3 @@ @import 'layout/sidebar'; // Pages - diff --git a/opentech/templates/base-apply.html b/opentech/templates/base-apply.html index 1d2903dd3c9f6a98fe151682ed8308e221339c40..5ebdb2b767151d11ca7f935601ab5c8f63b3095c 100644 --- a/opentech/templates/base-apply.html +++ b/opentech/templates/base-apply.html @@ -73,7 +73,7 @@ <svg class="icon"><use xlink:href="#person-icon"></use></svg> {{ request.user }} </a> - <a href="{% url 'users:logout' %}" class="link link--button-transparent link--mobile-standout">Logout</a> + <a href="{% url 'users_public:logout' %}" class="link link--button-transparent link--mobile-standout">Logout</a> </section> <div class="header__button-container"> @@ -81,7 +81,7 @@ <svg class="icon icon--person"><use xlink:href="#person-icon"></use></svg> {{ request.user }} </a> - <a href="{% url 'users:logout' %}" class="button button--transparent button--narrow"> + <a href="{% url 'users_public:logout' %}" class="button button--transparent button--narrow"> Log Out </a> </div> diff --git a/opentech/templates/base.html b/opentech/templates/base.html index 722aadef3d9d76b5a268d64bfcc17d4ed74c5a4f..0730652a903cbaaa9820a9c56e44515b8dab15bb 100644 --- a/opentech/templates/base.html +++ b/opentech/templates/base.html @@ -118,10 +118,7 @@ </section> <div class="header__button-container"> - <a href="{% url 'users:login' %}" class="button button--transparent button--contains-icons"> - <svg class="icon icon--person"><use xlink:href="#person-icon"></use></svg> - My OTF - </a> + {% include "utils/includes/login_button.html" %} <div class="button button--google-translate" id="google_translate_element"></div> </div> </div> @@ -153,34 +150,7 @@ <footer class="footer"> <div class="grid grid--two wrapper wrapper--large"> <div class="footer__inner"> - <h4>Get the latest internet freedom news</h4> - <form class="form"> - <div> - <div class="mailchimp-signup-subscribe-form-description"></div> - <div id="mailchimp-newsletter-32632431e3-mergefields" class="mailchimp-newsletter-mergefields"> - <div class="form-item form-type-textfield form-item-mergevars-EMAIL"> - <label for="edit-mergevars-email">Email Address <span class="form-required" title="This field is required.">*</span></label> - <input type="text" id="edit-mergevars-email" name="mergevars[EMAIL]" value="" size="25" maxlength="128" class="form-text required input--secondary"> - </div> - - <div class="form-item form-type-textfield form-item-mergevars-FNAME"> - <label for="edit-mergevars-fname">First Name </label> - <input type="text" id="edit-mergevars-fname" name="mergevars[FNAME]" value="" size="25" maxlength="128" class="form-text input--secondary"> - </div> - - <div class="form-item form-type-textfield form-item-mergevars-LNAME"> - <label for="edit-mergevars-lname">Last Name </label> - <input type="text" id="edit-mergevars-lname" name="mergevars[LNAME]" value="" size="25" maxlength="128" class="form-text input--secondary"> - </div> - - </div> - <input type="hidden" name="form_build_id" value="form-2Dy9x5istHLUmufjcHabtyuZ_niL-RlfSoHBIq39hpI"> - <input type="hidden" name="form_id" value="mailchimp_signup_subscribe_block_otf_newsletter_form"> - <div class="form-actions form-wrapper" id="edit-actions--3"> - <input type="submit" id="edit-submit--3" name="op" value="Sign up" class="form-submit link link--button-transparent link--footer-signup"> - </div> - </div> - </form> + {% include "mailchimp/newsletter_signup.html" %} </div> <div class="footer__inner"> @@ -199,12 +169,10 @@ <p> <a href="mailto:hello@opentech.fund">hello@opentech.fund</a></br> <a href="mailto:press@opentech.fund">press@opentech.fund</a></br> - <span>PGP: 67AC DDCF B909 4685 36DD BC03 F766 3861 965A 90D2</span> + <span>PGP: <a href="https://keybase.io/opentechfund/pgp_keys.asc?fingerprint=67acddcfb909468536ddbc03f7663861965a90d2">67AC DDCF B909 4685 36DD BC03 F766 3861 965A 90D2</a></span> </p> - <p><a href="/rss.xml">RSS Feed</a></p> - - <p><a class="link link--underlined" href="#">Terms of Use</a></p> + <p><a class="link link--underlined" href="/tos">Terms of Use</a></p> <p>Test the OTF website for censorship</p> diff --git a/opentech/urls.py b/opentech/urls.py index ccf767d79944263b3d5809335c7e0312f9530210..6ccf53e45e08630bc26257b714c8b501746210f4 100644 --- a/opentech/urls.py +++ b/opentech/urls.py @@ -11,7 +11,7 @@ from wagtail.core import urls as wagtail_urls from wagtail.documents import urls as wagtaildocs_urls from opentech.public import urls as public_urls -from opentech.apply import urls as apply_urls +from opentech.apply.users.urls import public_urlpatterns as user_urls urlpatterns = [ @@ -21,7 +21,7 @@ urlpatterns = [ path('documents/', include(wagtaildocs_urls)), path('sitemap.xml', sitemap), path('', include(public_urls)), - path('', include(apply_urls)), + path('', include((user_urls, 'users_public'))), path('', include('social_django.urls', namespace='social')), path('tinymce/', include('tinymce.urls')), path('select2/', include('django_select2.urls')), diff --git a/requirements.txt b/requirements.txt index 0ea9b0b33fa436dc7538579fffaa11c5336c4588..4a5561150c9302752af377776340b269fe8ce696 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ celery==4.2.1 factory_boy==2.9.2 # wagtail_factories - waiting on merge and release form master branch git+git://github.com/mvantellingen/wagtail-factories.git#egg=wagtail_factories -responses == 0.9.0 +responses==0.9.0 flake8 @@ -33,3 +33,4 @@ whitenoise==2.0.4 uwsgi==2.0.15 ConcurrentLogHandler==0.9.1 raven==6.9.0 +mailchimp3==3.0.4