From bb6e9b7643e17e558fedc9e8f6efd3f542191aa4 Mon Sep 17 00:00:00 2001 From: bansalnitish Date: Sat, 3 Mar 2018 23:27:25 +0530 Subject: [PATCH 1/6] feature to send abandoned project mail --- readthedocs/projects/forms.py | 14 +++++++++++++- readthedocs/projects/models.py | 17 +++++++++++++++++ readthedocs/projects/urls/private.py | 4 ++++ readthedocs/projects/views/private.py | 18 +++++++++++++++++- .../projects/email/abandon_project.html | 13 +++++++++++++ .../projects/email/abandon_project.txt | 9 +++++++++ .../templates/projects/import_base.html | 7 +++++++ 7 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 readthedocs/templates/projects/email/abandon_project.html create mode 100644 readthedocs/templates/projects/email/abandon_project.txt diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index caaeef9d0de..ee3e0d57752 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -114,7 +114,19 @@ def clean_name(self): name = self.cleaned_data.get('name', '') if not self.instance.pk: potential_slug = slugify(name) - if Project.objects.filter(slug=potential_slug).exists(): + project_exist = Project.objects.filter(slug=potential_slug).exists() + if project_exist: + project = Project.objects.get(slug=potential_slug) + for user in project.users.all(): + if user.is_superuser: + email = user.email + if project.is_abandoned: + self.fields['abandon'] = forms.CharField( + widget=forms.HiddenInput()) + self.fields['mail_id'] = forms.EmailField( + initial=email, widget=forms.HiddenInput()) + self.fields['proj_name'] = forms.CharField( + initial=name, widget=forms.HiddenInput()) raise forms.ValidationError( _('Invalid project name, a project already exists with that name')) # yapf: disable # noqa return name diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 5ff628e899f..a34308475e0 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -8,6 +8,7 @@ import logging import os from builtins import object # pylint: disable=redefined-builtin +from datetime import datetime from django.conf import settings from django.contrib.auth.models import User @@ -558,6 +559,22 @@ def conf_dir(self, version=LATEST): if conf_file: return os.path.dirname(conf_file) + @property + def is_abandoned(self): + """Is project abandoned.""" + if self.has_good_build: + latest_build = self.get_latest_build() + if latest_build: + latest_build_date = latest_build.date + today = datetime.today() + diff = today - latest_build_date + # Latest build a year ago. + if diff > 365: + return True + return False + return False + return True + @property def is_type_sphinx(self): """Is project type Sphinx.""" diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py index 7033e71a566..e582869cd36 100644 --- a/readthedocs/projects/urls/private.py +++ b/readthedocs/projects/urls/private.py @@ -103,6 +103,10 @@ url(r'^(?P[-\w]+)/advertising/$', ProjectAdvertisingUpdate.as_view(), name='projects_advertising'), + + url(r'^import/manual/send_mail/$', + private.send_mail, + name='send_mail'), ] domain_urls = [ diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 6db70fb6af5..5a76967e010 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -26,7 +26,7 @@ from readthedocs.builds.forms import AliasForm, VersionForm from readthedocs.builds.models import Version, VersionAlias from readthedocs.core.mixins import ListViewWithForm, LoginRequiredMixin -from readthedocs.core.utils import broadcast, trigger_build +from readthedocs.core.utils import broadcast, trigger_build, send_email from readthedocs.integrations.models import HttpExchange, Integration from readthedocs.oauth.services import registry from readthedocs.oauth.utils import attach_webhook, update_webhook @@ -264,6 +264,22 @@ def is_advanced(self): return data.get('advanced', True) +def send_mail(request): + """Sends abandoned project email.""" + email = request.POST.get('mail_id') + proj_name = request.POST.get('proj_name') + context = {'proj_name': proj_name} + subject = 'Rename request for abandoned project' + send_email( + recipient=email, + subject=subject, + template='projects/email/abandon_project.txt', + template_html='projects/email/abandon_project.html', + context=context) + messages.success(request, _('Mail sent!')) + return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) + + class ImportDemoView(PrivateViewMixin, View): """View to pass request on to import form to import demo project.""" diff --git a/readthedocs/templates/projects/email/abandon_project.html b/readthedocs/templates/projects/email/abandon_project.html new file mode 100644 index 00000000000..d80f2d1b9de --- /dev/null +++ b/readthedocs/templates/projects/email/abandon_project.html @@ -0,0 +1,13 @@ +{% extends "core/email/common.html" %} + +{% load i18n %} + +{% block content %} +

+ We've had a request from one of our users for the project name {{proj_name}} on Read the Docs. You are the current owner, and we wanted to reach out to you in accordance with our Abandoned Project policy (http://docs.readthedocs.io/en/latest/abandoned-projects.html). +

+ +

+ Please reply at hello@readthedocs.com either allowing or disallowing the transfer of the name on Read the Docs within 6 weeks, otherwise we will take the action of *initiating the transfer to a new owner* by default. +

+{% endblock %} diff --git a/readthedocs/templates/projects/email/abandon_project.txt b/readthedocs/templates/projects/email/abandon_project.txt new file mode 100644 index 00000000000..9398e1ec077 --- /dev/null +++ b/readthedocs/templates/projects/email/abandon_project.txt @@ -0,0 +1,9 @@ +{% extends "core/email/common.txt" %} + +{% load i18n %} + +{% block content %} +We've had a request from one of our users for the project name {{proj_name}} on Read the Docs. You are the current owner, and we wanted to reach out to you in accordance with our Abandoned Project policy (http://docs.readthedocs.io/en/latest/abandoned-projects.html). + +Please reply at hello@readthedocs.com either allowing or disallowing the transfer of the name on Read the Docs within 6 weeks, otherwise we will take the action of *initiating the transfer to a new owner* by default. +{% endblock %} diff --git a/readthedocs/templates/projects/import_base.html b/readthedocs/templates/projects/import_base.html index e6bb9c7f6c6..be9f800a374 100644 --- a/readthedocs/templates/projects/import_base.html +++ b/readthedocs/templates/projects/import_base.html @@ -5,6 +5,13 @@ {% block content %}
+ {% if wizard.form.fields.abandon %} +
+ {% csrf_token %} + + + + {% endif %} {% csrf_token %} From f8b862bd901e6278ab3ab54aa0a3ec5e4bea3030 Mon Sep 17 00:00:00 2001 From: bansalnitish Date: Mon, 5 Mar 2018 03:22:02 +0530 Subject: [PATCH 2/6] add link for sending mail on project page --- readthedocs/projects/forms.py | 17 ++++++--------- .../migrations/0024_add_abandon_mail_sent.py | 21 +++++++++++++++++++ readthedocs/projects/models.py | 10 +++++++-- readthedocs/projects/urls/private.py | 8 +++---- readthedocs/projects/views/private.py | 14 +++++++++---- .../templates/core/project_bar_base.html | 13 ++++++++++++ .../templates/projects/import_base.html | 7 ------- 7 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 readthedocs/projects/migrations/0024_add_abandon_mail_sent.py diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index ee3e0d57752..441ffd29cca 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -117,18 +117,13 @@ def clean_name(self): project_exist = Project.objects.filter(slug=potential_slug).exists() if project_exist: project = Project.objects.get(slug=potential_slug) - for user in project.users.all(): - if user.is_superuser: - email = user.email + project_url = project.get_absolute_url() if project.is_abandoned: - self.fields['abandon'] = forms.CharField( - widget=forms.HiddenInput()) - self.fields['mail_id'] = forms.EmailField( - initial=email, widget=forms.HiddenInput()) - self.fields['proj_name'] = forms.CharField( - initial=name, widget=forms.HiddenInput()) - raise forms.ValidationError( - _('Invalid project name, a project already exists with that name')) # yapf: disable # noqa + err_msg = ('Invalid project name, a ' + 'project already exists with that name').format(project_url) + else: + err_msg = 'Invalid project name, a project already exists with that name' + raise forms.ValidationError(mark_safe(err_msg)) # yapf: disable # noqa return name def clean_repo(self): diff --git a/readthedocs/projects/migrations/0024_add_abandon_mail_sent.py b/readthedocs/projects/migrations/0024_add_abandon_mail_sent.py new file mode 100644 index 00000000000..3f1ab9b80c5 --- /dev/null +++ b/readthedocs/projects/migrations/0024_add_abandon_mail_sent.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2018-03-04 09:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0023_migrate-alias-slug'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='abandoned_mail_sent', + field=models.BooleanField(default=False), + ), + + ] diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index a34308475e0..badcfd3ff5f 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -8,7 +8,7 @@ import logging import os from builtins import object # pylint: disable=redefined-builtin -from datetime import datetime +from datetime import datetime, timedelta from django.conf import settings from django.contrib.auth.models import User @@ -83,6 +83,7 @@ class Project(models.Model): related_name='projects') name = models.CharField(_('Name'), max_length=255) slug = models.SlugField(_('Slug'), max_length=255, unique=True) + abandoned_mail_sent = models.BooleanField(default=False) description = models.TextField(_('Description'), blank=True, help_text=_('The reStructuredText ' 'description of the project')) @@ -569,12 +570,17 @@ def is_abandoned(self): today = datetime.today() diff = today - latest_build_date # Latest build a year ago. - if diff > 365: + if diff > timedelta(days=365): return True return False return False return True + @property + def is_abandoned_mail_sent(self): + """Is abandoned mail sent.""" + return self.abandoned_mail_sent + @property def is_type_sphinx(self): """Is project type Sphinx.""" diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py index e582869cd36..f70ee13de3e 100644 --- a/readthedocs/projects/urls/private.py +++ b/readthedocs/projects/urls/private.py @@ -36,6 +36,10 @@ private.project_manage, name='projects_manage'), + url(r'^(?P[-\w]+)/send_mail/$', + private.send_mail, + name='send_mail'), + url(r'^(?P[-\w]+)/comments_moderation/$', private.project_comments_moderation, name='projects_comments_moderation'), @@ -103,10 +107,6 @@ url(r'^(?P[-\w]+)/advertising/$', ProjectAdvertisingUpdate.as_view(), name='projects_advertising'), - - url(r'^import/manual/send_mail/$', - private.send_mail, - name='send_mail'), ] domain_urls = [ diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 5a76967e010..b7d9e5a73af 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -27,6 +27,7 @@ from readthedocs.builds.models import Version, VersionAlias from readthedocs.core.mixins import ListViewWithForm, LoginRequiredMixin from readthedocs.core.utils import broadcast, trigger_build, send_email +from readthedocs.core.permissions import AdminPermission from readthedocs.integrations.models import HttpExchange, Integration from readthedocs.oauth.services import registry from readthedocs.oauth.utils import attach_webhook, update_webhook @@ -264,10 +265,14 @@ def is_advanced(self): return data.get('advanced', True) -def send_mail(request): +@login_required +def send_mail(request, project_slug): """Sends abandoned project email.""" - email = request.POST.get('mail_id') - proj_name = request.POST.get('proj_name') + project = Project.objects.get(slug=project_slug) + proj_name = project_slug + for user in project.users.all(): + if AdminPermission.is_admin(user, project): + email = user.email context = {'proj_name': proj_name} subject = 'Rename request for abandoned project' send_email( @@ -276,7 +281,8 @@ def send_mail(request): template='projects/email/abandon_project.txt', template_html='projects/email/abandon_project.html', context=context) - messages.success(request, _('Mail sent!')) + project.abandoned_mail_sent = True + project.save() return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) diff --git a/readthedocs/templates/core/project_bar_base.html b/readthedocs/templates/core/project_bar_base.html index 1224442eed4..1e2d3c60348 100644 --- a/readthedocs/templates/core/project_bar_base.html +++ b/readthedocs/templates/core/project_bar_base.html @@ -23,6 +23,19 @@

{{ project }}

+ + {% if project.is_abandoned and request.user.is_superuser %} +
+ {% if not project.is_abandoned_mail_sent %} + + {% csrf_token %} + + + {% else %} +

{%trans "Abandonment mail is sent to the owner of the project." %}

+ {% endif %} +
+ {% endif %}
diff --git a/readthedocs/templates/projects/import_base.html b/readthedocs/templates/projects/import_base.html index be9f800a374..e6bb9c7f6c6 100644 --- a/readthedocs/templates/projects/import_base.html +++ b/readthedocs/templates/projects/import_base.html @@ -5,13 +5,6 @@ {% block content %}
- {% if wizard.form.fields.abandon %} -
- {% csrf_token %} - - - - {% endif %} {% csrf_token %} From 38ef44d135056f74aae0a71c42800f90688cfda0 Mon Sep 17 00:00:00 2001 From: bansalnitish Date: Mon, 5 Mar 2018 03:22:40 +0530 Subject: [PATCH 3/6] add test --- readthedocs/rtd_tests/tests/test_project.py | 76 +++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/readthedocs/rtd_tests/tests/test_project.py b/readthedocs/rtd_tests/tests/test_project.py index 38e7943634d..e4a9933fb69 100644 --- a/readthedocs/rtd_tests/tests/test_project.py +++ b/readthedocs/rtd_tests/tests/test_project.py @@ -1,3 +1,4 @@ + # -*- coding: utf-8 -*- from __future__ import ( absolute_import, division, print_function, unicode_literals) @@ -216,3 +217,78 @@ def test_finish_inactive_builds_task(self): self.assertTrue(self.build_3.success) self.assertEqual(self.build_3.error, '') self.assertEqual(self.build_3.state, BUILD_STATE_TRIGGERED) + + +class TestAbandonedProject(TestCase): + fixtures = ['eric', 'test_data'] + + def setUp(self): + self.client.login(username='eric', password='test') + self.pip = Project.objects.get(slug='pip') + self.taggit = Project.objects.get(slug='taggit') + self.pinax = Project.objects.get(slug='pinax') + + self.build_1 = Build.objects.create( + project=self.pip, + version=self.pip.get_stable_version(), + state=BUILD_STATE_FINISHED, + ) + + self.build_1.date = ( + datetime.datetime.now() - datetime.timedelta(days=750)) + self.build_1.success = False + self.build_1.save() + + self.build_2 = Build.objects.create( + project=self.pip, + version=self.pip.get_stable_version(), + state=BUILD_STATE_FINISHED, + ) + + self.build_2.success = True + self.build_2.save() + + self.build_3 = Build.objects.create( + project=self.taggit, + version=self.taggit.get_stable_version(), + state=BUILD_STATE_FINISHED, + ) + + self.build_3.date = ( + datetime.datetime.now() - datetime.timedelta(days=2)) + self.build_3.success = False + self.build_3.save() + + self.build_4 = Build.objects.create( + project=self.taggit, + version=self.taggit.get_stable_version(), + state=BUILD_STATE_FINISHED, + ) + + self.build_4.success = False + self.build_4.save() + + self.build_5 = Build.objects.create( + project=self.pinax, + version=self.pinax.get_stable_version(), + state=BUILD_STATE_FINISHED, + ) + + self.build_5.success = False + self.build_5.save() + + self.build_6 = Build.objects.create( + project=self.pinax, + version=self.pinax.get_stable_version(), + state=BUILD_STATE_FINISHED, + ) + + self.build_6.date = ( + datetime.datetime.now() - datetime.timedelta(days=750)) + self.build_6.success = True + self.build_6.save() + + def test_abandoned_project(self): + self.assertFalse(self.pip.is_abandoned) + self.assertTrue(self.taggit.is_abandoned) + self.assertFalse(self.pinax.is_abandoned) From a4974afc7a7e63090867a62d38c62682dddb96de Mon Sep 17 00:00:00 2001 From: bansalnitish Date: Mon, 5 Mar 2018 21:09:41 +0530 Subject: [PATCH 4/6] fix test --- readthedocs/rtd_tests/tests/test_privacy_urls.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py index e992a55ae1c..71bc818eb35 100644 --- a/readthedocs/rtd_tests/tests/test_privacy_urls.py +++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py @@ -219,6 +219,7 @@ class PrivateProjectAdminAccessTest(PrivateProjectMixin, TestCase): # Places where we 302 on success -- These delete pages should probably be 405'ing '/dashboard/import/manual/demo/': {'status_code': 302}, '/dashboard/pip/': {'status_code': 302}, + '/dashboard/pip/send_mail/': {'status_code':302}, '/dashboard/pip/subprojects/delete/sub/': {'status_code': 302}, '/dashboard/pip/translations/delete/sub/': {'status_code': 302}, @@ -253,6 +254,7 @@ class PrivateProjectUserAccessTest(PrivateProjectMixin, TestCase): # Unauth access redirect for non-owners '/dashboard/pip/': {'status_code': 302}, + '/dashboard/pip/send_mail/': {'status_code':302}, # 405's where we should be POST'ing '/dashboard/pip/users/delete/': {'status_code': 405}, From 543e3e6a69659cf1601c1bb00b621657a46b1005 Mon Sep 17 00:00:00 2001 From: bansalnitish Date: Tue, 6 Mar 2018 10:16:00 +0530 Subject: [PATCH 5/6] requested changes --- readthedocs/projects/views/private.py | 1 + readthedocs/templates/core/project_bar_base.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index b7d9e5a73af..a5dc808b0d6 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -273,6 +273,7 @@ def send_mail(request, project_slug): for user in project.users.all(): if AdminPermission.is_admin(user, project): email = user.email + break context = {'proj_name': proj_name} subject = 'Rename request for abandoned project' send_email( diff --git a/readthedocs/templates/core/project_bar_base.html b/readthedocs/templates/core/project_bar_base.html index 1e2d3c60348..916b31834c3 100644 --- a/readthedocs/templates/core/project_bar_base.html +++ b/readthedocs/templates/core/project_bar_base.html @@ -32,7 +32,7 @@

{% else %} -

{%trans "Abandonment mail is sent to the owner of the project." %}

+

{%trans "Abandonment mail was sent to the owner of the project." %}

{% endif %}

{% endif %} From 8e2f573c2c4f69b1fd33984da0db1542b9401106 Mon Sep 17 00:00:00 2001 From: bansalnitish Date: Thu, 8 Mar 2018 17:12:14 +0530 Subject: [PATCH 6/6] requested changes --- readthedocs/projects/forms.py | 7 ++---- readthedocs/projects/models.py | 5 ---- readthedocs/projects/urls/private.py | 6 ++--- readthedocs/projects/views/private.py | 23 +++++++++---------- .../rtd_tests/tests/test_privacy_urls.py | 4 ++-- .../templates/core/project_bar_base.html | 4 ++-- 6 files changed, 20 insertions(+), 29 deletions(-) diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 441ffd29cca..3c5952a6b76 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -118,11 +118,8 @@ def clean_name(self): if project_exist: project = Project.objects.get(slug=potential_slug) project_url = project.get_absolute_url() - if project.is_abandoned: - err_msg = ('Invalid project name, a ' - 'project already exists with that name').format(project_url) - else: - err_msg = 'Invalid project name, a project already exists with that name' + err_msg = ('Invalid project name, a ' + 'project already exists with that name').format(project_url) raise forms.ValidationError(mark_safe(err_msg)) # yapf: disable # noqa return name diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index badcfd3ff5f..9847d644d37 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -576,11 +576,6 @@ def is_abandoned(self): return False return True - @property - def is_abandoned_mail_sent(self): - """Is abandoned mail sent.""" - return self.abandoned_mail_sent - @property def is_type_sphinx(self): """Is project type Sphinx.""" diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py index f70ee13de3e..fc263fc474b 100644 --- a/readthedocs/projects/urls/private.py +++ b/readthedocs/projects/urls/private.py @@ -36,9 +36,9 @@ private.project_manage, name='projects_manage'), - url(r'^(?P[-\w]+)/send_mail/$', - private.send_mail, - name='send_mail'), + url(r'^(?P[-\w]+)/send_abandoned_mail/$', + private.send_abandoned_mail, + name='send_abandoned_mail'), url(r'^(?P[-\w]+)/comments_moderation/$', private.project_comments_moderation, diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index a5dc808b0d6..dd24e4cd058 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -8,7 +8,7 @@ from allauth.socialaccount.models import SocialAccount from django.contrib import messages -from django.contrib.auth.decorators import login_required +from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.http import ( @@ -266,22 +266,21 @@ def is_advanced(self): @login_required -def send_mail(request, project_slug): +@user_passes_test(lambda u: u.is_superuser) +def send_abandoned_mail(request, project_slug): """Sends abandoned project email.""" project = Project.objects.get(slug=project_slug) proj_name = project_slug - for user in project.users.all(): - if AdminPermission.is_admin(user, project): - email = user.email - break context = {'proj_name': proj_name} subject = 'Rename request for abandoned project' - send_email( - recipient=email, - subject=subject, - template='projects/email/abandon_project.txt', - template_html='projects/email/abandon_project.html', - context=context) + for user in project.users.all(): + email = user.email + send_email( + recipient=email, + subject=subject, + template='projects/email/abandon_project.txt', + template_html='projects/email/abandon_project.html', + context=context) project.abandoned_mail_sent = True project.save() return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py index 71bc818eb35..2991c3dd713 100644 --- a/readthedocs/rtd_tests/tests/test_privacy_urls.py +++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py @@ -219,7 +219,7 @@ class PrivateProjectAdminAccessTest(PrivateProjectMixin, TestCase): # Places where we 302 on success -- These delete pages should probably be 405'ing '/dashboard/import/manual/demo/': {'status_code': 302}, '/dashboard/pip/': {'status_code': 302}, - '/dashboard/pip/send_mail/': {'status_code':302}, + '/dashboard/pip/send_abandoned_mail/': {'status_code':302}, '/dashboard/pip/subprojects/delete/sub/': {'status_code': 302}, '/dashboard/pip/translations/delete/sub/': {'status_code': 302}, @@ -254,7 +254,7 @@ class PrivateProjectUserAccessTest(PrivateProjectMixin, TestCase): # Unauth access redirect for non-owners '/dashboard/pip/': {'status_code': 302}, - '/dashboard/pip/send_mail/': {'status_code':302}, + '/dashboard/pip/send_abandoned_mail/': {'status_code':302}, # 405's where we should be POST'ing '/dashboard/pip/users/delete/': {'status_code': 405}, diff --git a/readthedocs/templates/core/project_bar_base.html b/readthedocs/templates/core/project_bar_base.html index 916b31834c3..1cffc49e319 100644 --- a/readthedocs/templates/core/project_bar_base.html +++ b/readthedocs/templates/core/project_bar_base.html @@ -26,8 +26,8 @@

{% if project.is_abandoned and request.user.is_superuser %}
- {% if not project.is_abandoned_mail_sent %} -
+ {% if not project.abandoned_mail_sent %} + {% csrf_token %}