Skip to content

Commit

Permalink
UI for core affiliation management #3168
Browse files Browse the repository at this point in the history
  • Loading branch information
joemull committed Dec 12, 2024
1 parent 986781f commit 9bc1f96
Show file tree
Hide file tree
Showing 18 changed files with 636 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/core/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
AccessRequestForm,
AccountRoleForm,
AdminUserForm,
AffiliationForm,
ArticleMetaImageForm,
CBVFacetForm,
ConfirmableForm,
Expand Down
15 changes: 15 additions & 0 deletions src/core/forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -940,3 +940,18 @@ class AccountRoleForm(forms.ModelForm):
class Meta:
model = models.AccountRole
fields = '__all__'


class AffiliationForm(forms.ModelForm):

class Meta:
model = models.Affiliation
fields = '__all__'
widgets = {
'account': forms.HiddenInput,
'frozen_author': forms.HiddenInput,
'preprint_author': forms.HiddenInput,
'organization': forms.HiddenInput,
'start': HTMLDateInput,
'end': HTMLDateInput,
}
32 changes: 32 additions & 0 deletions src/core/include_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,38 @@
re_path(r'^manager/user/(?P<user_id>\d+)/edit/$', core_views.user_edit, name='core_user_edit'),
re_path(r'^manager/user/(?P<user_id>\d+)/history/$', core_views.user_history, name='core_user_history'),

## Affiliations
re_path(
r'^profile/organization/search/$',
core_views.OrganizationListView.as_view(),
name='core_organization_search'
),
re_path(
r'^profile/organization_name/create/$',
core_views.OrganizationNameCreateView.as_view(),
name='core_organization_name_create'
),
re_path(
r'^profile/organization_name/(?P<organization_name_id>\d+)/update/$',
core_views.OrganizationNameUpdateView.as_view(),
name='core_organization_name_update'
),
re_path(
r'^profile/organization/(?P<organization_id>\d+)/affiliation/create/$',
core_views.AffiliationCreateView.as_view(),
name='core_affiliation_create'
),
re_path(
r'^profile/affiliation/(?P<affiliation_id>\d+)/update/$',
core_views.AffiliationUpdateView.as_view(),
name='core_affiliation_update'
),
re_path(
r'^profile/affiliation/(?P<affiliation_id>\d+)/delete/$',
core_views.AffiliationDeleteView.as_view(),
name='core_affiliation_delete'
),

# Templates
re_path(r'^manager/templates/$', core_views.email_templates, name='core_email_templates'),

Expand Down
154 changes: 153 additions & 1 deletion src/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@
from django.contrib.auth import authenticate, logout, login
from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.urls import NoReverseMatch, reverse
from django.urls import NoReverseMatch, reverse, reverse_lazy
from django.shortcuts import render, get_object_or_404, redirect, Http404
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.http import HttpResponse, QueryDict
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.sessions.models import Session
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.conf import settings as django_settings
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic import CreateView, UpdateView, DeleteView
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _
from django.utils import translation
Expand Down Expand Up @@ -2775,3 +2777,153 @@ def post(self, request, *args, **kwargs):
messages.success(request, message)

return super().post(request, *args, **kwargs)


@method_decorator(login_required, name='dispatch')
class OrganizationListView(GenericFacetedListView):
"""
Allows a user to search for an organization to add
as one of their own affiliations.
"""

model = core_models.Organization
template_name = 'admin/core/organization_search.html'

def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['account'] = self.request.user
return context

def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
# Exclude user-created organizations from search results
return queryset.exclude(custom_label__isnull=False)

def get_facets(self):
return {
'q': {
'type': 'search',
'field_label': 'Search',
},
}


@method_decorator(login_required, name='dispatch')
class OrganizationNameCreateView(SuccessMessageMixin, CreateView):
"""
Allows a user to create a custom organization name
if they cannot find one in ROR data.
"""

model = core_models.OrganizationName
fields = ['value']

def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['account'] = self.request.user
return context

def form_valid(self, form):
organization_name = form.save()
organization = core_models.Organization.objects.create()
organization_name.custom_label_for = organization
organization_name.save()
return redirect(
reverse(
'affiliation_create',
kwargs={
'user_id': self.kwargs.get('user_id'),
'organization_id': organization.pk,
}
)
)


@method_decorator(login_required, name='dispatch')
class OrganizationNameUpdateView(SuccessMessageMixin, UpdateView):
"""
Allows a user to update a custom organization name.
"""

model = core_models.OrganizationName
fields = ['value']
pk_url_kwarg = 'organization_name_id'
success_url = reverse_lazy('core_edit_profile')
success_message = _("Custom organization updated: %(value)s")

def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['account'] = self.request.user
return context


@method_decorator(login_required, name='dispatch')
class AffiliationCreateView(SuccessMessageMixin, CreateView):
"""
Allows a user to create a new affiliation for themselves.
"""

model = core_models.Affiliation
form_class = forms.AffiliationForm
success_url = reverse_lazy('core_edit_profile')
success_message = _("Affiliation created: %(organization)s")

def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['account'] = self.request.user
organization_id = self.kwargs.get('organization_id')
context['organization'] = core_models.Organization.objects.get(
pk=organization_id
)
return context

def get_initial(self):
initial = super().get_initial()
initial['account'] = self.request.user
initial['organization'] = self.kwargs.get('organization_id')
return initial


@method_decorator(login_required, name='dispatch')
class AffiliationUpdateView(SuccessMessageMixin, UpdateView):
"""
Allows a user to update one of their own affiliations.
"""

model = core_models.Affiliation
pk_url_kwarg = 'affiliation_id'
form_class = forms.AffiliationForm
success_url = reverse_lazy('core_edit_profile')
success_message = _("Affiliation updated: %(organization)s")

def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['account'] = self.request.user
affiliation_id = self.kwargs.get('affiliation_id')
context['affiliation'] = core_models.Affiliation.objects.get(
pk=affiliation_id
)
context['organization'] = context['affiliation'].organization
return context


@method_decorator(login_required, name='dispatch')
class AffiliationDeleteView(SuccessMessageMixin, DeleteView):
"""
Allows a user to delete one of their own affiliations.
"""

model = core_models.Affiliation
pk_url_kwarg = 'affiliation_id'
success_url = reverse_lazy('core_edit_profile')
success_message = _("Affiliation deleted")

def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['account'] = self.request.user
affiliation_id = self.kwargs.get('affiliation_id')
context['affiliation'] = core_models.Affiliation.objects.get(
pk=affiliation_id
)
context['organization'] = context['affiliation'].organization
return context
19 changes: 19 additions & 0 deletions src/static/admin/img/icons/ror-icon-rgb.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/static/common/css/utilities.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
&.items-start { align-items: flex-start; }
&.items-center { align-items: center; }

/* Flex direction */
&.direction-row { flex-direction: row; }
&.direction-column { flex-direction: column; }

/* Responsive flex direction */
@media (orientation: portrait) {
&.portrait-row { flex-direction: row; }
Expand Down
26 changes: 24 additions & 2 deletions src/templates/admin/core/accounts/edit_profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
{% load i18n foundation static %}

{% block css %}
<link type='text/css' href="{% static " common/css/jq-ui.css" %}" rel="stylesheet">
<link type='text/css' href="{% static "common/css/jq-ui.css" %}" rel="stylesheet">
{% endblock %}

{% block contextual_title %}
{% trans "Edit Profile" %}
{% endblock contextual_title %}

{% block breadcrumbs %}
<li>Edit Profile</li>
{% endblock breadcrumbs %}

{% block title-section %}
Expand Down Expand Up @@ -117,6 +116,29 @@ <h2>{% trans "Update Password" %}</h2>
</form>
{% include "admin/elements/forms/denotes_required.html" %}
</section>
<section class="card padding-block-2 padding-inline-2">
<div class="title-area">
<h2>{% trans "Affiliations" %}</h2>
</div>
<div class="flex direction-column gap-0-5 no-bottom-margin">
{% for affiliation in request.user.affiliations %}
<div class="flex wrap column-gap-2 row-gap-0-5 items-center space-between">
{% include "admin/core/affiliation_display.html" with affiliation=affiliation %}
<div class="flex gap-0-5 items-center">
{% url 'core_affiliation_update' affiliation.pk as update_url %}
{% include "elements/a_edit.html" with href=update_url size="small" %}
{% url 'core_affiliation_delete' affiliation.pk as delete_url %}
{% include "elements/a_delete.html" with href=delete_url size="small" %}
</div>
</div>
{% endfor %}
<div>
{% url 'core_organization_search' as create_url %}
{% trans "Add affiliation" as add_affiliation %}
{% include "elements/a_create.html" with href=create_url size="small" label=add_affiliation %}
</div>
</div>
</section>
<section class="card padding-block-2 padding-inline-2">
<div class="title-area">
<h2>{% trans 'Profile Details' %}</h2>
Expand Down
62 changes: 62 additions & 0 deletions src/templates/admin/core/affiliation_confirm_delete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{% extends "admin/core/large_form.html" %}

{% load i18n foundation %}

{% block contextual_title %}
{% blocktrans with organization=affiliation.organization.name %}
Delete affiliation: {{ organization }}
{% endblocktrans %}
{% endblock contextual_title %}

{% block title-section %}
{% blocktrans with organization=affiliation.organization.name %}
Delete affiliation: {{ organization }}
{% endblocktrans %}
{% endblock title-section %}

{% block breadcrumbs %}
{% if request.user == account %}
<li>
<a href="{% url 'core_edit_profile' %}">
{% trans "Edit Profile" %}
</a>
</li>
<li>
<a href="{% url 'core_affiliation_update' object.pk %}">
{% trans "Edit Affiliation" %}
</a>
</li>
<li>
{% blocktrans with organization=affiliation.organization.name %}
Delete affiliation: {{ organization }}
{% endblocktrans %}
</li>
{% endif %}
{% endblock breadcrumbs %}

{% block body %}
<div class="grid max-w-80">
<div class="rummage-portal">
{% include "admin/core/affiliation_summary.html" %}
<section class="card padding-block-2 padding-inline-2">
<div class="title-area">
<h2>{% trans "Confirmation" %}</h2>
</div>
{% include "admin/elements/forms/messages_in_callout.html" with form=form %}
<form method="post">
{% csrf_token %}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
<p>{% blocktrans %}
Are you sure you want to delete the affiliation "{{ object }}"?
{% endblocktrans %}</p>
{{ form }}
{% include "elements/button_yes_delete.html" %}
{% url 'core_edit_profile' as go_back_url %}
{% include "elements/a_no_go_back.html" with href=go_back_url %}
</form>
</section>
</div>
</div>
{% endblock body %}
Loading

0 comments on commit 9bc1f96

Please sign in to comment.