Skip to content

Commit

Permalink
Merge branch 'feature/b-and-i-24-14' of https://github.com/CenterForO…
Browse files Browse the repository at this point in the history
…penScience/osf.io into fix-source-tags

* 'feature/b-and-i-24-14' of https://github.com/CenterForOpenScience/osf.io:
  refactor handle_duplicate_notifications and add tests
  Shorten lines; rename script for test clarity
  fix flake8 errors
  add admin screen to manage duplicate notifications
  [ENG-2814] Allow Read-only and Read/Write contributors to view a project's draft registrations (CenterForOpenScience#10660)
  [CR][ENG-5997] merge develop into b-and-i branch (CenterForOpenScience#10691)
  add exception handling in case state doesn't change
  [ENG-4527] Fix citation to use registered date (CenterForOpenScience#10678)
  restrict state changes more and allow no-ops
  split apart change provider views from general preprint view and machine_state change viewa
  Re-add permissions changes for files on withdrawn registrations (CenterForOpenScience#10671)
  [ENG-4903] Fixes issue with email confirmation links failing due to database congestion (CenterForOpenScience#10662)

# Conflicts:
#	addons/boa/requirements.txt
#	addons/box/requirements.txt
#	addons/dataverse/requirements.txt
#	addons/dropbox/requirements.txt
#	addons/github/requirements.txt
#	addons/gitlab/requirements.txt
#	addons/mendeley/requirements.txt
#	addons/owncloud/requirements.txt
#	addons/s3/requirements.txt
#	addons/twofactor/requirements.txt
#	addons/wiki/requirements.txt
#	addons/zotero/requirements.txt
#	api/base/utils.py
#	api/users/serializers.py
#	api_tests/draft_registrations/views/test_draft_registration_list.py
#	api_tests/nodes/views/test_node_draft_registration_list.py
#	api_tests/users/views/test_user_draft_registration_list.py
#	api_tests/users/views/test_user_settings.py
#	docker-compose.yml
#	poetry.lock
#	pyproject.toml
#	requirements.txt
#	requirements/dev.txt
#	requirements/release.txt
#	tests/test_views.py
#	website/util/metrics.py
  • Loading branch information
John Tordoff committed Aug 13, 2024
2 parents 60d793c + b5d0d3c commit 497424e
Show file tree
Hide file tree
Showing 57 changed files with 1,605 additions and 1,074 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.cache
key: reqs_${{ hashFiles('**/pyproject.toml') }}
key: reqs_${{ hashFiles('**/requirements.txt') }}
restore-keys: reqs
- run: |
mkdir -p ~/.cache/downloads
Expand Down
3 changes: 3 additions & 0 deletions addons/base/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ def get_authenticated_resource(resource_id):
if resource.deleted:
raise HTTPError(http_status.HTTP_410_GONE, message='Resource has been deleted.')

if getattr(resource, 'is_retracted', False):
raise HTTPError(http_status.HTTP_410_GONE, message='Resource has been retracted.')

return resource


Expand Down
5 changes: 5 additions & 0 deletions addons/boa/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Requirements for the boa add-on
boa-api==0.1.14

# Requirements for running asyncio in celery, using 3.4.1 for Python 3.6 compatibility
asgiref==3.7.2
1 change: 1 addition & 0 deletions addons/box/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
boxsdk==3.9.2
3 changes: 3 additions & 0 deletions addons/dataverse/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Allow for optional timeout parameter.
# https://github.com/IQSS/dataverse-client-python/pull/27
git+https://github.com/CenterForOpenScience/dataverse-client-python.git@2b3827578048e6df3818f82381c7ea9a2395e526 # branch is feature/dv-client-updates
1 change: 1 addition & 0 deletions addons/dropbox/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dropbox==11.36.2
3 changes: 3 additions & 0 deletions addons/github/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cachecontrol==0.14.0
github3.py==4.0.1
uritemplate==4.1.1
1 change: 1 addition & 0 deletions addons/gitlab/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python-gitlab==4.4.0
2 changes: 2 additions & 0 deletions addons/mendeley/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# up-to-date with mendeley's master + add folder support and future dep updates
git+https://github.com/CenterForOpenScience/mendeley-python-sdk.git@be8a811fa6c3b105d9f5c656cabb6b1ba855ed5b # branch is feature/osf-dep-updates
Empty file.
2 changes: 2 additions & 0 deletions addons/owncloud/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Requirements for the owncloud add-on
pyocclient==0.6.0
1 change: 1 addition & 0 deletions addons/s3/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
boto3==1.34.60
1 change: 1 addition & 0 deletions addons/twofactor/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pyotp==2.9.0
1 change: 1 addition & 0 deletions addons/wiki/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pymongo==4.6.3
1 change: 1 addition & 0 deletions addons/zotero/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pyzotero==1.5.18
1 change: 1 addition & 0 deletions admin/base/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
re_path(r'^schema_responses/', include('admin.schema_responses.urls', namespace='schema_responses')),
re_path(r'^registration_schemas/', include('admin.registration_schemas.urls', namespace='registration_schemas')),
re_path(r'^cedar_metadata_templates/', include('admin.cedar.urls', namespace='cedar_metadata_templates')),
re_path(r'^notifications/', include('admin.notifications.urls', namespace='notifications')),
]),
),
]
Expand Down
8 changes: 8 additions & 0 deletions admin/notifications/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.urls import re_path
from admin.notifications import views

app_name = 'notifications'

urlpatterns = [
re_path(r'^$', views.handle_duplicate_notifications, name='handle_duplicate_notifications'),
]
54 changes: 54 additions & 0 deletions admin/notifications/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from django.contrib.auth.decorators import user_passes_test
from django.shortcuts import render, redirect
from admin.base.utils import osf_staff_check
from osf.models.notifications import NotificationSubscription
from django.db.models import Count

def delete_selected_notifications(selected_ids):
NotificationSubscription.objects.filter(id__in=selected_ids).delete()

def detect_duplicate_notifications():
duplicates = (
NotificationSubscription.objects.values('user', 'node', 'event_name')
.annotate(count=Count('id'))
.filter(count__gt=1)
)

detailed_duplicates = []
for dup in duplicates:
notifications = NotificationSubscription.objects.filter(
user=dup['user'], node=dup['node'], event_name=dup['event_name']
).order_by('created')

for notification in notifications:
detailed_duplicates.append({
'id': notification.id,
'user': notification.user,
'node': notification.node,
'event_name': notification.event_name,
'created': notification.created,
'count': dup['count']
})

return detailed_duplicates

def process_duplicate_notifications(request):
detailed_duplicates = detect_duplicate_notifications()

if request.method == 'POST':
selected_ids = request.POST.getlist('selected_notifications')
delete_selected_notifications(selected_ids)
return detailed_duplicates, 'Selected duplicate notifications have been deleted.', True

return detailed_duplicates, '', False

@user_passes_test(osf_staff_check)
def handle_duplicate_notifications(request):
detailed_duplicates, message, is_post = process_duplicate_notifications(request)

context = {'duplicates': detailed_duplicates}
if is_post:
context['message'] = message
return redirect('notifications:handle_duplicate_notifications')

return render(request, 'notifications/handle_duplicate_notifications.html', context)
26 changes: 25 additions & 1 deletion admin/preprints/forms.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
from django import forms

from osf.models import Preprint

from osf.utils.workflows import ReviewStates

class ChangeProviderForm(forms.ModelForm):
class Meta:
model = Preprint
fields = ('provider',)


class MachineStateForm(forms.ModelForm):
class Meta:
model = Preprint
fields = ('machine_state',)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

if not self.instance.is_public:
self.fields['machine_state'].widget.attrs['disabled'] = 'disabled'
else:
if self.instance.machine_state == ReviewStates.INITIAL.db_name:
self.fields['machine_state'].choices = [
(ReviewStates.INITIAL.value, ReviewStates.INITIAL.value),
(ReviewStates.PENDING.value, ReviewStates.PENDING.value),
]
else:
# Disabled Option you are on
self.fields['machine_state'].widget.attrs['disabled'] = 'disabled'
self.fields['machine_state'].choices = [
(self.instance.machine_state.title(), self.instance.machine_state)
]
2 changes: 2 additions & 0 deletions admin/preprints/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
re_path(r'^known_ham$', views.PreprintKnownHamList.as_view(), name='known-ham'),
re_path(r'^withdrawal_requests$', views.PreprintWithdrawalRequestList.as_view(), name='withdrawal-requests'),
re_path(r'^(?P<guid>[a-z0-9]+)/$', views.PreprintView.as_view(), name='preprint'),
re_path(r'^(?P<guid>[a-z0-9]+)/change_provider/$', views.PreprintProviderChangeView.as_view(), name='preprint-provider'),
re_path(r'^(?P<guid>[a-z0-9]+)/machine_state/$', views.PreprintMachineStateView.as_view(), name='preprint-machine-state'),
re_path(r'^(?P<guid>[a-z0-9]+)/reindex_share_preprint/$', views.PreprintReindexShare.as_view(),
name='reindex-share-preprint'),
re_path(r'^(?P<guid>[a-z0-9]+)/remove_user/(?P<user_id>[a-z0-9]+)/$', views.PreprintRemoveContributorView.as_view(),
Expand Down
42 changes: 35 additions & 7 deletions admin/preprints/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from admin.base.views import GuidView
from admin.base.forms import GuidForm
from admin.nodes.views import NodeRemoveContributorView
from admin.preprints.forms import ChangeProviderForm
from admin.preprints.forms import ChangeProviderForm, MachineStateForm

from api.share.utils import update_share

Expand Down Expand Up @@ -62,6 +62,21 @@ class PreprintView(PreprintMixin, GuidView):
"""
template_name = 'preprints/preprint.html'
permission_required = ('osf.view_preprint', 'osf.change_preprint',)

def get_context_data(self, **kwargs):
preprint = self.get_object()
return super().get_context_data(**{
'preprint': preprint,
'SPAM_STATUS': SpamStatus,
'change_provider_form': ChangeProviderForm(instance=preprint),
'change_machine_state_form': MachineStateForm(instance=preprint),
}, **kwargs)


class PreprintProviderChangeView(PreprintMixin, GuidView):
""" Allows authorized users to view preprint info and change a preprint's provider.
"""
permission_required = ('osf.view_preprint', 'osf.change_preprint',)
form_class = ChangeProviderForm

def post(self, request, *args, **kwargs):
Expand All @@ -79,13 +94,26 @@ def post(self, request, *args, **kwargs):

return redirect(self.get_success_url())

def get_context_data(self, **kwargs):

class PreprintMachineStateView(PreprintMixin, GuidView):
""" Allows authorized users to view preprint info and change a preprint's machine_state.
"""
permission_required = ('osf.view_preprint', 'osf.change_preprint',)
form_class = MachineStateForm

def post(self, request, *args, **kwargs):
preprint = self.get_object()
return super().get_context_data(**{
'preprint': preprint,
'SPAM_STATUS': SpamStatus,
'form': ChangeProviderForm(instance=preprint),
}, **kwargs)
new_machine_state = request.POST.get('machine_state')
if new_machine_state and preprint.machine_state != new_machine_state:
preprint.machine_state = new_machine_state
try:
preprint.save()
except Exception as e:
messages.error(self.request, e.message)

preprint.refresh_from_db()

return redirect(self.get_success_url())


class PreprintSearchView(PermissionRequiredMixin, FormView):
Expand Down
3 changes: 3 additions & 0 deletions admin/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@
{% if perms.osf.change_maintenancestate %}
<li><a href="{% url 'maintenance:display' %}"><i class='fa fa-link'></i> <span>Maintenance Alerts</span></a></li>
{% endif %}
{% if perms.osf.view_notification %}
<li><a href="{% url 'notifications:handle_duplicate_notifications' %}"><i class='fa fa-link'></i><span>Duplicate Notifications</span> </a></li>
{% endif %}
</ul><!-- /.sidebar-menu -->
</section>
<!-- /.sidebar -->
Expand Down
54 changes: 54 additions & 0 deletions admin/templates/notifications/handle_duplicate_notifications.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% load static %}

{% block title %}
<title>Duplicate Notifications</title>
{% endblock title %}

{% block content %}
<h2>Duplicate Notifications</h2>

{% if message %}
<div class="alert alert-success">
{{ message }}
</div>
{% endif %}

{% if duplicates %}
<form method="post">
{% csrf_token %}
<table class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th>Select</th>
<th>User</th>
<th>Node</th>
<th>Event Name</th>
<th>Created</th>
<th>Count</th>
</tr>
</thead>
<tbody>
{% for notification in duplicates %}
<tr>
<td><input type="checkbox" name="selected_notifications" value="{{ notification.id }}"></td>
<td>{{ notification.user }}</td>
<td>{{ notification.node }}</td>
<td>{{ notification.event_name }}</td>
<td>{{ notification.created }}</td>
<td>{{ notification.count }}</td>
</tr>
{% empty %}
<tr>
<td colspan="6">No duplicate notifications found!</td>
</tr>
{% endfor %}
</tbody>
</table>
<button type="submit" class="btn btn-danger">Delete Selected</button>
</form>
{% else %}
<p>No duplicate notifications found.</p>
{% endif %}
{% endblock content %}
22 changes: 22 additions & 0 deletions admin/templates/preprints/machine_state.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% load node_extras %}
<tr>
<td>Machine State</td>
<td>
<h4 >{{ preprint.machine_state }}</h4>
<h4 >{{ preprint.state }}</h4>
{% if perms.osf.change_preprint %}
<a class="btn btn-link" role="button" data-toggle="collapse" href="#collapseChangeMachineState">
Change preprint machine_state
</a>
<div class="collapse" id="collapseChangeMachineState">
<div class="well">
<form action="{% url 'preprints:preprint-machine-state' guid=preprint.guid %}" method="post">
{% csrf_token %}
{{ change_machine_state_form.as_p }}
<input class="btn-btn-primary" type="submit" value="Submit" />
</form>
</div>
</div>
{% endif %}
</td>
</tr>
5 changes: 1 addition & 4 deletions admin/templates/preprints/preprint.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ <h2>Preprint: <b>{{ preprint.title }}</b> <a href="{{ preprint.absolute_url }}">
<td>Published</td>
<td>{{ preprint.is_published }}</td>
</tr>
<tr>
<td>Machine State</td>
<td>{{ preprint.machine_state }}</td>
</tr>
{% if preprint.is_published %}
<tr>
<td>Date Published</td>
Expand All @@ -104,6 +100,7 @@ <h2>Preprint: <b>{{ preprint.title }}</b> <a href="{{ preprint.absolute_url }}">
</tr>
{% endif %}
{% include "preprints/provider.html" with preprint=preprint %}
{% include "preprints/machine_state.html" with preprint=preprint %}
<tr>
<td>Subjects</td>
<td>
Expand Down
4 changes: 2 additions & 2 deletions admin/templates/preprints/provider.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
</a>
<div class="collapse" id="collapseChangeProvider">
<div class="well">
<form action="{% url 'preprints:preprint' guid=preprint.guid %}" method="post">
<form action="{% url 'preprints:preprint-provider' guid=preprint.guid %}" method="post">
{% csrf_token %}
{{ form.as_p }}
{{ change_provider_form.as_p }}
<input class="btn-btn-primary" type="submit" value="Submit" />
</form>
</div>
Expand Down
Empty file.
Loading

0 comments on commit 497424e

Please sign in to comment.