# SPDX-FileCopyrightText: 2024 Thomas Breitner <t.breitner@csl.mpg.de>
#
# SPDX-License-Identifier: EUPL-1.2
from django.db import models
from django.db.models import Count, Q
from django.utils.html import format_html
from django.contrib.auth import get_user_model
from wagtail.models import Page, PageManager
from wagtail.fields import StreamField
from wagtail.admin.panels import FieldPanel, MultiFieldPanel, FieldRowPanel
from wagtail.search import index
from django_countries.fields import CountryField
from django_select2.forms import Select2Widget
from eucrim.core.abstracts import BasePage
from eucrim.core.models.mixins import HideShowInMenusField
from eucrim.core.blocks import BaseStreamBlock
from eucrim.users.models import CustomUser
[docs]
class ProfileIndexPage(HideShowInMenusField, BasePage):
parent_page_types = ["core.HomePage"]
subpage_types = ["ProfilePage"]
max_count = 1
show_in_menus = True
body = StreamField(
BaseStreamBlock,
verbose_name="Page body",
blank=True,
use_json_field=True,
)
[docs]
def get_context(self, request, *args, **kwargs):
context = super().get_context(request)
context["authors"] = (
ProfilePage.author_objects.live().public().select_related("avatar")
)
context["profile_index_page"] = self
return context
content_panels = Page.content_panels + [
FieldPanel("body"),
]
promote_panels = HideShowInMenusField.promote_panels
[docs]
class ProfilePageManager(PageManager): # pylint: disable=too-few-public-methods
"""Custom manager for Profile pages"""
[docs]
def get_queryset(self):
author_articles = Count(
"articlepageauthor__page", # traverse via ArticlePageAuthor to the ArticlePage
distinct=True,
filter=Q(articlepageauthor__page__live=True),
)
author_news = Count("news", distinct=True, filter=Q(news__live=True))
return (
super()
.get_queryset()
.annotate(
# articles_count=Count('articles', distinct=True),
# news_count=Count('news', distinct=True),
articles_count=author_articles,
news_count=author_news,
)
.order_by("slug", "last_name", "first_name")
)
[docs]
class AuthorPageManager(ProfilePageManager): # pylint: disable=too-few-public-methods
"""Custom manager for Author pages"""
[docs]
def get_queryset(self):
return super().get_queryset().filter(user__is_author=True)
[docs]
class TeamPageManager(ProfilePageManager): # pylint: disable=too-few-public-methods
"""Custom manager for Team pages"""
[docs]
def get_queryset(self):
return super().get_queryset().filter(user__is_team=True)
[docs]
class ProfilePage(BasePage):
parent_page_types = ["profile.ProfileIndexPage"]
subpage_types = []
show_in_menus = False
# Normally ProfilPages are created via signal when a user (as an author)
# is created. But to add legacy profiles manually, we allow this:
is_creatable = True
user = models.OneToOneField(
get_user_model(), null=True, blank=True, on_delete=models.SET_NULL
) # editable=False, on_delete=models.PROTECT
# mirrored user model fields:
last_name = models.CharField(
max_length=30,
db_index=True,
)
first_name = models.CharField(
max_length=30,
db_index=True,
)
email = models.EmailField(
"email address",
blank=True,
help_text="The email address (and some other personal data) is only visible for authenticated users.",
)
is_author = models.BooleanField(
default=True,
help_text="Is this profile an author profile?",
)
is_team = models.BooleanField(
default=False,
help_text="Is this profile a team member profile?",
)
is_association_manager = models.BooleanField(
default=False,
help_text="Is this user an association manager?",
)
team_role = models.CharField(
max_length=6,
choices=CustomUser.TEAM_ROLE_CHOICES,
default=CustomUser.NOROLE,
help_text='Role in eucrim team, like stated in the imprint of the printed issue; e.g. "Managing Editor"',
)
# custom profile fields:
academic_title = models.CharField(
max_length=100, blank=True, help_text='Academic titles and/or grades like "Dr."'
)
academic_posttitle = models.CharField(
max_length=100,
blank=True,
help_text='Academic titles and/or grades like "LL.M.", normally written after the name.',
)
country = CountryField(blank=True)
orcid_id = models.CharField(
max_length=19, blank=True, help_text="Format: <code>0000-0001-5000-000X</code>"
)
website = models.URLField(
blank=True,
verbose_name="Personal website",
)
institution = models.CharField(max_length=255, blank=True)
institution_url = models.URLField(
blank=True,
verbose_name="Institution URL",
)
institution_abbr = models.CharField(
max_length=255,
blank=True,
verbose_name="Institution abbreviation",
)
department = models.CharField(max_length=255, blank=True)
position = models.CharField(max_length=255, blank=True)
about = models.TextField(
blank=True, help_text='Short bio, used as an "about" text on post pages'
)
avatar = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
# Model managers:
objects = ProfilePageManager() # The default manager.
author_objects = AuthorPageManager() # Returns only authors
team_objects = TeamPageManager()
@property
def get_full_name(self):
return "{pre_title} {first_name} {last_name} {post_title}".format(
last_name=self.last_name,
first_name=self.first_name,
pre_title=self.academic_title,
post_title=self.academic_posttitle,
)
@property
def get_full_name_wo_titles(self):
return "{last_name}, {first_name}".format(
last_name=self.last_name,
first_name=self.first_name,
)
@property
def get_institution(self):
institution_ref = ""
if not self.institution:
institution_ref = None
elif self.institution_abbr and self.institution_url:
institution_ref = format_html(
'<a href="{url}">{name} (<abbr title="{name}">{abbr}</abbr>)</a>',
name=self.institution,
abbr=self.institution_abbr,
url=self.institution_url,
)
elif self.institution_abbr and not self.institution_url:
institution_ref = format_html(
'{name} (<abbr title="{name}">{abbr}</abbr>)',
name=self.institution,
abbr=self.institution_abbr,
)
elif self.institution_url and not self.institution_abbr:
institution_ref = format_html(
'<a href="{url}">{name}</a>',
name=self.institution,
url=self.institution_url,
)
elif self.institution:
institution_ref = self.institution
return institution_ref
@property
def get_articles(self):
# return self.articles.live().public().distinct().order_by("-first_published_at")
# Get all ArticlePage objects where this profile is an author
from django.apps import apps
ArticlePage = apps.get_model("article", "ArticlePage")
return (
ArticlePage.objects.live()
.public()
.filter(article_authors__author=self)
.distinct()
.order_by("issue")
)
@property
def get_news(self):
return self.news.live().public().distinct().order_by("-first_published_at")
@property
def get_obj_og_description(self):
return self.about
@property
def get_obj_og_image(self):
return self.avatar
[docs]
def arcana_markdown(self):
from .export_markdown import export_profile_to_markdown
return export_profile_to_markdown(self)
def __str__(self):
return "{last_name}{delimiter}{first_name}".format(
last_name=self.last_name,
first_name=self.first_name,
delimiter=", " if self.first_name and self.last_name else "",
)
[docs]
def save(self, *args, **kwargs):
self.title = self.get_full_name_wo_titles
super().save(*args, **kwargs)
content_panels = [
# FieldPanel('user'),
# FieldPanel('team_role'),
FieldPanel(
"is_author", permission="profile.can_add_profilepages_without_user_relation"
),
MultiFieldPanel(
[
FieldRowPanel(
[
FieldPanel("first_name"),
FieldPanel("last_name"),
]
),
FieldRowPanel(
[
FieldPanel("academic_title"),
FieldPanel("academic_posttitle"),
]
),
FieldRowPanel(
[
FieldPanel("email"),
FieldPanel("website"),
]
),
FieldRowPanel(
[
FieldPanel(
"country",
widget=Select2Widget(),
),
FieldPanel("orcid_id"),
]
),
FieldPanel("avatar"),
]
),
FieldPanel("about"),
MultiFieldPanel(
[
FieldPanel("institution", classname="col12"),
FieldPanel("institution_url", classname="col6"),
FieldPanel("institution_abbr", classname="col6"),
FieldPanel("department", classname="col12"),
FieldPanel("position", classname="col12"),
],
heading="Insititution",
classname="collapsible",
),
]
# Profile slug should be auto generated and profile pages should
# never show_in_menus: disabling the whole promote_panels
promote_panels = []
search_fields = Page.search_fields + [
index.AutocompleteField("first_name"),
index.AutocompleteField("last_name"),
index.SearchField("orcid_id"),
index.AutocompleteField("position"),
]
class Meta:
verbose_name = "Profile"
verbose_name_plural = "Profiles"
ordering = ["slug", "last_name", "first_name"]
permissions = [
(
"can_add_profilepages_without_user_relation",
"Can add ProfilePages without user relation",
),
]