Commit 8e93449f authored by Hervé  MENAGER's avatar Hervé MENAGER
Browse files

further work on contributor page and sitemap

- create a new Contributor model, which is just a proxy class for User
- finalize first version of the contributor page
- add contributors to sitemap
- bonus: improve sitemaps to generate only url and lastmod attributes
  (the only ones used by search engines) and use last validated contribution
  for lastmod
parent 8a7fda8d
Pipeline #72747 failed with stage
in 10 minutes and 8 seconds
......@@ -34,6 +34,7 @@ from .ippidb import (
DrugBankCompound,
DrugbankCompoundTanimoto,
Contribution,
Contributor,
create_tanimoto,
update_compound_cached_properties,
get_user_model,
......@@ -87,6 +88,7 @@ __all__ = [
DrugBankCompound,
DrugbankCompoundTanimoto,
Contribution,
Contributor,
create_tanimoto,
update_compound_cached_properties,
get_user_model,
......
......@@ -17,6 +17,7 @@ from django.db.models import FloatField, IntegerField, BooleanField
from django.db.models import Max, Count, F, Q, Case, When, Subquery, OuterRef, Exists
from django.db.models.functions import Cast
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django_celery_results.models import TaskResult
from django.dispatch import receiver
......@@ -35,6 +36,7 @@ from ippidb.ws import (
get_chembl_id,
get_pubchem_id,
get_ligand_id,
get_orcid_user_details,
EntryNotFoundError,
)
......@@ -1290,6 +1292,15 @@ class Compound(AutoFillableModel):
)
return results
def get_last_modification_date(self):
"""
Return last modification date for the compound
based on latest contribution date
"""
return Contribution.objects.filter(
bibliography__refcompoundbiblio__compound=self
).aggregate(Max("created_at"))["created_at__max"]
class CompoundTanimoto(models.Model):
canonical_smiles = models.TextField("Canonical Smile")
......@@ -2118,3 +2129,131 @@ class CompoundJob(models.Model):
def post_save_taskresult(sender, instance, created, *args, **kwargs):
if created:
Job.objects.create(task_result=instance)
User = get_user_model()
class Contributor(User):
class Meta:
proxy = True
@cached_property
def orcid_json(self):
return get_orcid_user_details(self.get_orcid())
def get_orcid(self):
"""
Get contributor ORCID from their allauth social account
"""
if self.socialaccount_set.count() > 0:
return self.socialaccount_set.all()[0].uid
return None
def get_orcid_url(self):
"""
Get contributor ORCID fURL from their allauth profile
"""
if self.socialaccount_set.count() > 0:
return self.socialaccount_set.all()[0].get_profile_url()
return None
def get_contributor_firstname(self):
"""
Get contributor full name from their ORCID profile
"""
orcid = self.get_orcid()
if orcid is not None:
data = self.orcid_json
return data["first_name"]
def get_contributor_lastname(self):
"""
Get contributor full name from their ORCID profile
"""
orcid = self.get_orcid()
if orcid is not None:
data = self.orcid_json
return data["last_name"]
def get_contributor_keywords(self):
"""
Get contributor keywords from their ORCID profile
"""
orcid = self.get_orcid()
if orcid is not None:
data = self.orcid_json
return data["keywords"]
def get_validated_contributions(self):
"""
Get validated contributions for the user
"""
return self.contribution_set.filter(validated=True)
def get_validated_patents_count(self):
"""
Get number of publications in validated contributions for the user
"""
return Bibliography.objects.filter(
source__in=["PT"],
contribution__validated=True,
contribution__contributor=self,
).count()
def get_validated_publications_count(self):
"""
Get number of publications in validated contributions for the user
"""
return Bibliography.objects.filter(
source__in=["PM", "DO"],
contribution__validated=True,
contribution__contributor=self,
).count()
def get_validated_bibliographies(self):
"""
Get publications/patents in validated contributions for the user
"""
return Bibliography.objects.filter(
contribution__validated=True,
contribution__contributor=self,
)
def get_validated_compounds_count(self):
"""
Get number of compounds in validated contributions for the user
"""
return Compound.objects.filter(
compoundaction__ppi__contribution__validated=True,
compoundaction__ppi__contribution__contributor=self,
).count()
def get_validated_compoundactivityresults_count(self):
"""
Get number of compound activity results in validated contributions for the user
"""
return CompoundActivityResult.objects.filter(
test_activity_description__biblio__contribution__validated=True,
test_activity_description__biblio__contribution__contributor=self,
).count()
def get_validated_ppis_count(self):
"""
Get number of PPIs in validated contributions for the user
"""
return Ppi.objects.filter(
contribution__validated=True, contribution__contributor=self
).count()
def get_absolute_url(self):
return reverse("contributor_card", kwargs={"pk": self.pk})
def get_last_modification_date(self):
"""
Return last modification date for the contributor
based on latest contribution date
"""
return Contribution.objects.filter(
contributor=self, validated=True
).aggregate(Max("created_at"))["created_at__max"]
......@@ -5,7 +5,44 @@
{% block pagetitle %}Contributors{% endblock%}
{% block view_content %}
<div class="row">
{{ object.get_contributor_firstname | capfirst }} {{ object.get_contributor_lastname | capfirst }} || {{ object.get_orcid_url }}
<div class="inner-wrap">
<div class="container-fluid inner-wrap compound_header_title mx-auto p-4" id="compound_header_title">
<div class="container">
<div class="row">
<h1 class="page-title" style="margin: 0;">{{ object.get_contributor_firstname | capfirst }} {{ object.get_contributor_lastname | capfirst }}</h1>
</div>
<div class="row">
<h2><a href="{{ object.get_orcid_url }}" target="_blank">{{ object.get_orcid }}</a></h2>
</div>
</div>
</div>
</div>
{% if object.get_contributor_keywords %}
<div class="col-sm-12">
<h4 class="pt-2 compound_list_title">Expertises</h4>
<ul class="list-group">
<li class="list-group-item">{{ object.get_contributor_keywords }}</li>
</ul>
</div>
{% endif %}
<div class="col-sm-12">
<h4 class="pt-2 compound_list_title">Published contributions number</h4>
<ul class="list-group">
<li class="list-group-item">{{ object.get_validated_publications_count }} publications</li>
<li class="list-group-item">{{ object.get_validated_patents_count }} patents</li>
<li class="list-group-item">{{ object.get_validated_compounds_count }} compounds</li>
<li class="list-group-item">{{ object.get_validated_compoundactivityresults_count }} pharmacological data</li>
<li class="list-group-item">{{ object.get_validated_ppis_count }} PPI targets</li>
</ul>
</div>
<div class="col-sm-12">
<h4 class="pt-2 compound_list_title">Scientific publications and patents</h4>
<ul class="list-group">
{% for bibliography in object.get_validated_bibliographies %}
<li class="list-group-item">{% include "biblio_citation.html" with bibliography=bibliography %}</li>
{% endfor %}
</ul>
</div>
{% endblock %}
\ No newline at end of file
......@@ -8,19 +8,28 @@
<div class="row">
<table class="table table-striped">
<thead>
<tr>
<th scope="col" title="Contributor Name">Name</th>
<th scope="col" title="Contributor ORCID Link">ORCID</th>
</tr>
<tr>
<th scope="col" title="Contributor Name">Name</th>
<th scope="col" title="Contributor ORCID Link">ORCID</th>
</tr>
</thead>
<tbody>
{% for contributor in object_list %}
<tr>
<td><a href="{{ contributor.id }}">{{ contributor.get_contributor_firstname | capfirst }} {{ contributor.get_contributor_lastname | capfirst }}</a></td>
<td><a href="{{ contributor.get_orcid_url }}" target="_blank">{{ contributor.get_orcid }}</a></td>
<td>
<a href="{{ contributor.id }}">
{{ contributor.get_contributor_firstname | capfirst }}
{{ contributor.get_contributor_lastname | capfirst }}
</a>
</td>
<td>
<a href="{{ contributor.get_orcid_url }}" target="_blank">
{{ contributor.get_orcid }}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</table>
</div>
{% endblock %}
\ No newline at end of file
......@@ -10,10 +10,9 @@
{% block content %}
<div id="mainnav">
<nav class="secondary-nav">
<div class="inner-wrap">
<ul>
<li>
<a href="/about-general">General information</a>
<a href="/about-general">General</a>
</li>
<li>
<a href="/about-pharmacology">Pharmacology</a>
......@@ -28,10 +27,9 @@
<a href="/about-pca">Chemical space</a>
</li>
<li>
<a href="/contributors">Contributors</a>
<a href="/about-contributors">Contributors</a>
</li>
</ul>
</div>
</nav>
</div>
......
......@@ -45,6 +45,7 @@ class IppiDbStaticSitemap(Sitemap):
sitemaps = {
"static": IppiDbStaticSitemap,
"compounds": views.IppiDbCompoundSitemap,
"contributors": views.IppiDbContributorsSitemap,
}
urlpatterns = [
......
......@@ -23,7 +23,7 @@ from .compound_query import (
convert_iupac2smi,
)
from .site import IppiDbCompoundSitemap
from .site import IppiDbCompoundSitemap, IppiDbContributorsSitemap
from .tasks import get_output_job, get_json_network
from .about import (
......@@ -113,4 +113,5 @@ __all__ = [
NetworkView,
get_json_network,
IppiDbCompoundSitemap,
IppiDbContributorsSitemap,
]
......@@ -2,7 +2,6 @@
iPPI-DB global statistics views
"""
from django.contrib.auth import get_user_model
from django.db.models import F, Count, FloatField, QuerySet
from django.db.models.functions import Cast, Floor
from django.shortcuts import render
......@@ -20,10 +19,9 @@ from ippidb.models import (
CompoundActivityResult,
Bibliography,
Protein,
Contributor,
)
from ippidb.ws import get_orcid_user_details
def about_general(request):
compounds_count = Compound.objects.count()
......@@ -285,66 +283,6 @@ def about_pca(request):
return render(request, "about-pca.html", context=context)
User = get_user_model()
def cached_orcid_json(getter_function):
def new_function(self):
if self.__orcid_json is None:
self.__orcid_json = get_orcid_user_details(self.get_orcid())
ret = getter_function(self)
return ret
return new_function
def get_orcid(self):
"""
Get contributor ORCID from their allauth social account
"""
if self.socialaccount_set.count() > 0:
return self.socialaccount_set.all()[0].uid
return None
def get_orcid_url(self):
"""
Get contributor ORCID fURL from their allauth profile
"""
if self.socialaccount_set.count() > 0:
return self.socialaccount_set.all()[0].get_profile_url()
return None
@cached_orcid_json
def get_contributor_firstname(self):
"""
Get contributor full name from their ORCID profile
"""
orcid = self.get_orcid()
if orcid is not None:
data = self.__orcid_json
return data["first_name"]
@cached_orcid_json
def get_contributor_lastname(self):
"""
Get contributor full name from their ORCID profile
"""
orcid = self.get_orcid()
if orcid is not None:
data = self.__orcid_json
return data["last_name"]
User.add_to_class("get_orcid", get_orcid)
User.add_to_class("get_orcid_url", get_orcid_url)
User.add_to_class("__orcid_json", None)
User.add_to_class("get_contributor_firstname", get_contributor_firstname)
User.add_to_class("get_contributor_lastname", get_contributor_lastname)
class ContributorListView(ListView):
"""
Contributors list view
......@@ -352,10 +290,10 @@ class ContributorListView(ListView):
This view lists the users with at least one accepted contribution
"""
model = User
model = Contributor
template_name = "about-contributors.html"
def get_queryset(self) -> QuerySet[User]:
def get_queryset(self) -> QuerySet[Contributor]:
qs = super().get_queryset()
qs = qs.annotate(contributions_count=Count("contribution")).filter(
socialaccount__isnull=False, contributions_count__gt=0
......@@ -369,7 +307,7 @@ class ContributorDetailView(DetailView):
"""
model = User
model = Contributor
template_name = "about-contributor-card.html"
def get_queryset(self):
......
from django.contrib.sitemaps import Sitemap
from ippidb.models import Compound
from ippidb.models import Compound, Contributor
from django.db.models import Count
class IppiDbCompoundSitemap(Sitemap):
changefreq = "never"
priority = 0.5
def items(self):
return Compound.objects.validated()
def lastmod(self, obj):
return None
return obj.get_last_modification_date()
class IppiDbContributorsSitemap(Sitemap):
def items(self):
return (
Contributor.objects.all()
.annotate(contributions_count=Count("contribution"))
.filter(socialaccount__isnull=False, contributions_count__gt=0)
)
def lastmod(self, obj):
return obj.get_last_modification_date()
......@@ -5,6 +5,7 @@ from typing import List
import xml.etree.ElementTree as ET
from urllib import parse as urllib_parse
from django.views.generic import detail
import requests
from bioservices.eutils import EUtils
from bioservices.uniprot import UniProt
......@@ -597,7 +598,12 @@ def get_orcid_user_details(orcid: str) -> str:
headers={"Accept": "application/json"},
)
data = resp.json()
return {
details = {
"first_name": data["name"]["given-names"]["value"],
"last_name": data["name"]["family-name"]["value"],
}
try:
details["keywords"] = data["keywords"]["keyword"][0]["content"]
except Exception:
details["keywords"] = None
return details
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment