Source code for eucrim.profile.models

# 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", ), ]