Skip to content

Commit

Permalink
Merge branch 'release/4.20'
Browse files Browse the repository at this point in the history
  • Loading branch information
blms committed Feb 3, 2025
2 parents 9d9aa88 + f39f5e4 commit fd3cfa4
Show file tree
Hide file tree
Showing 45 changed files with 2,471 additions and 503 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
Change Log
==========

4.20
----

- public site
- As a public user, I want a keyword search in the people module so that I can easily find people entries.
- As a public user, I want to see related places on a place page on the main individual place page (not in a separate tab) so that I can see associated neighborhoods and also places that have similar names but are distinct
- As a public site user, I want any JA or Arabic search to link to the equivalent search on the Arabic Papyrology Database website, so that I can find additional content not present in the PGP.
- As a public user and content admin, I want to see two separate automatic date fields for people: one of only documents where they are mentioned as deceased and one with all other dated person-doc relations, so that I have a better understanding of a person's active dates and their afterlives in the documentary record.
- bugfix: Translations in Hebrew script do not pick up correct Hebrew font

- admin
- As a content admin, I want to be able to merge person-to-person relationship types, so that I can combine duplicates or revise categorization.
- As a content admin, I want to merge person-document relationship types, so that I can keep the website current as our thinking changes (but without losing data)
- As a content editor, I want to be able to tag people with various group names so that I can sort them in another way/portray more information on the public site.
- As a content admin, I want the ability to enter asymmetrical place-place relations, so that I can adapt to changes in the way we sort and represent data (e.g. representing a neighborhood within a place).
- As a content admin, when merging documents (for joins) I want to see image thumbnails of each document so I can be sure the join is correct.

4.19
----

Expand Down
5 changes: 5 additions & 0 deletions DEPLOYNOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Deploy Notes

## 4.20

- Solr configuration has changed. Ensure Solr configset has been updated
and then reindex all content: `python manage.py index`

## 4.19

- Indexing logic has changed. Reindex all content: `python manage.py index`.
Expand Down
2 changes: 1 addition & 1 deletion geniza/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version_info__ = (4, 19, 0, None)
__version_info__ = (4, 20, 0, None)


# Dot-connect all but the last. Last is dash-connected if not None.
Expand Down
15 changes: 15 additions & 0 deletions geniza/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.contrib.auth.models import User
from django.db import models
from django.db.models.functions.text import Lower
from django.utils.safestring import mark_safe
from modeltranslation.utils import fallbacks

Expand Down Expand Up @@ -110,3 +111,17 @@ def objects_by_label(cls):
(obj.display_label_en or obj.name_en): obj
for obj in cls.objects.all()
}


class TaggableMixin:
"""Mixin for taggable models with convenience functions for generating lists of tags"""

def all_tags(self):
"""comma delimited string of all tags for this instance"""
return ", ".join(t.name for t in self.tags.all())

all_tags.short_description = "tags"

def alphabetized_tags(self):
"""tags in alphabetical order, case-insensitive sorting"""
return self.tags.order_by(Lower("name"))
14 changes: 2 additions & 12 deletions geniza/corpus/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from django.core.validators import RegexValidator
from django.db import models
from django.db.models.functions import Concat
from django.db.models.functions.text import Lower
from django.db.models.query import Prefetch
from django.db.models.signals import pre_delete
from django.dispatch import receiver
Expand All @@ -41,6 +40,7 @@
from geniza.annotations.models import Annotation
from geniza.common.models import (
DisplayLabelMixin,
TaggableMixin,
TrackChangesModel,
cached_class_property,
)
Expand Down Expand Up @@ -521,7 +521,7 @@ def permalink(self):
return absolutize_url(self.get_absolute_url().replace(f"/{lang}/", "/"))


class Document(ModelIndexable, DocumentDateMixin, PermalinkMixin):
class Document(ModelIndexable, DocumentDateMixin, PermalinkMixin, TaggableMixin):
"""A unified document such as a letter or legal document that
appears on one or more fragments."""

Expand Down Expand Up @@ -749,16 +749,6 @@ def formatted_citation(self):
f"{long_name}. {available_at} {self.permalink}, accessed {today}."
)

def all_tags(self):
"""comma delimited string of all tags for this document"""
return ", ".join(t.name for t in self.tags.all())

all_tags.short_description = "tags"

def alphabetized_tags(self):
"""tags in alphabetical order, case-insensitive sorting"""
return self.tags.order_by(Lower("name"))

def is_public(self):
"""admin display field indicating if doc is public or suppressed"""
return self.status == self.PUBLIC
Expand Down
6 changes: 6 additions & 0 deletions geniza/corpus/templates/corpus/document_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ <h2>
{% if is_paginated %}
{% include "corpus/snippets/pagination.html" %}
{% endif %}
{% if apd_link %}
<a id="apd" href="{{ apd_link }}">
{# translators: Link to search a document query on the Arabic Papyrology Database #}
{% translate 'View results in the Arabic Papyrology Database' %}
</a>
{% endif %}
</section>
</form>
{% endblock main %}
13 changes: 10 additions & 3 deletions geniza/corpus/templates/corpus/snippets/document_option_label.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{% load corpus_extras %}
{# template snippet for displaying document label on a form #}
{# used on document merge form to provide enough information to merge accurately #}

<div class="merge-document-label">
<h2>{{ document }}
<a target="_blank" href="{% url 'admin:corpus_document_change' document.id %}"
title="Go to this document's admin edit page">
title="Go to this document's admin edit page">
<img src="/static/admin/img/icon-changelink.svg" alt="Change"></a>
</h2>
{% if document.description %}
Expand Down Expand Up @@ -32,6 +33,12 @@ <h2>{{ document }}
<label>Needs Review</label><div>{{ document.needs_review }}</div>
</div>
{% endif %}
{% if document.iiif_images %}
<div class="form-row">
<label>Images</label>
{{ document.admin_thumbnails }}
</div>
{% endif %}
{% if document.footnotes.count %}
<div class="form-row">
<label>Scholarship Records</label>
Expand All @@ -43,8 +50,8 @@ <h2>{{ document }}
<label>Bibliographic citation</label>
<div>{{ source.grouper.formatted_display|safe }}
<a href="{% url 'admin:footnotes_source_change' source.grouper.id %}"
title="Go to this source's admin edit page"><img src="/static/admin/img/icon-changelink.svg"
alt="Change"></a>
title="Go to this source's admin edit page"><img src="/static/admin/img/icon-changelink.svg"
alt="Change"></a>
{% if source.grouper.source_type.type == "Unpublished" %}
<div class="unpublished">unpublished</div>
{% endif %}
Expand Down
14 changes: 14 additions & 0 deletions geniza/corpus/tests/test_corpus_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,20 @@ def test_hebrew_prefix_highlight(self, source, empty_solr):
0
] == clean_html("<em>מרכב</em>")

def test_get_apd_link(self):
dsv = DocumentSearchView(kwargs={})

# no arabic or ja: bail out
assert not dsv.get_apd_link(None)
assert not dsv.get_apd_link("test")

# arabic: leave as is
arabic = "العبد"
assert dsv.get_apd_link(arabic) == f"{dsv.apd_base_url}{arabic}"

# JA: translate with regex
assert dsv.get_apd_link("ואגב") == f"{dsv.apd_base_url}وا[غج]ب"


class TestDocumentScholarshipView:
def test_page_title(self, document, client, source):
Expand Down
22 changes: 22 additions & 0 deletions geniza/corpus/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from geniza.common.utils import absolutize_url
from geniza.corpus import iiif_utils
from geniza.corpus.forms import DocumentMergeForm, DocumentSearchForm, TagMergeForm
from geniza.corpus.ja import contains_arabic, contains_hebrew, ja_arabic_chars
from geniza.corpus.models import Document, TextBlock
from geniza.corpus.solr_queryset import DocumentSolrQuerySet
from geniza.corpus.templatetags import corpus_extras
Expand Down Expand Up @@ -353,6 +354,26 @@ def get_paginate_by(self, queryset):
pass
return paginate_by

# base url for APD searches
apd_base_url = "https://www.apd.gwi.uni-muenchen.de/apd/asearch.jsp?searchtable1=601&showdwords=true&searchwordstring1="

def get_apd_link(self, query):
"""Generate a link to the Arabic Papyrology Database (APD) search page
using the entered query, converting any Hebrew script to Arabic with Regex"""
if not query or not (contains_arabic(query) or contains_hebrew(query)):
# if no arabic OR hebrew in query, bail out
return None
# simplified version of ja_to_arabic that uses regex instead of solr OR
for k, v in ja_arabic_chars.items():
if type(v) == list:
# list means there is more than one option, so join options with regex
query = re.sub(k, f"[{''.join(v)}]", query)
elif type(v) == str:
# only one possible translation
query = re.sub(k, v, query)
query = query.strip()
return f"{self.apd_base_url}{query}"

def get_context_data(self, **kwargs):
"""extend context data to add page metadata, highlighting,
and update form with facets"""
Expand Down Expand Up @@ -387,6 +408,7 @@ def get_context_data(self, **kwargs):
"page_includes_transcriptions": True, # preload transcription font
"highlighting": highlights,
"applied_filters": self.applied_filter_labels,
"apd_link": self.get_apd_link(context_data["form"].data.get("q", None)),
}
)

Expand Down
91 changes: 80 additions & 11 deletions geniza/entities/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@
PlacePlaceRelation,
PlacePlaceRelationType,
)
from geniza.entities.views import PersonMerge
from geniza.entities.views import (
PersonDocumentRelationTypeMerge,
PersonMerge,
PersonPersonRelationTypeMerge,
)
from geniza.footnotes.models import Footnote


Expand Down Expand Up @@ -263,17 +267,27 @@ class PersonEventInline(admin.TabularInline):
class PersonAdmin(TabbedTranslationAdmin, SortableAdminBase, admin.ModelAdmin):
"""Admin for Person entities in the PGP"""

list_display = (
"__str__",
"slug",
"gender",
"role",
"all_tags",
"has_page",
)
search_fields = ("name_unaccented", "names__name")
fields = (
"slug",
"gender",
"role",
"has_page",
"date",
"automatic_date",
"active_dates",
"deceased_mention_dates",
"description",
"tags",
)
readonly_fields = ("automatic_date",)
readonly_fields = ("active_dates", "deceased_mention_dates")
inlines = (
NameInline,
FootnoteInline,
Expand Down Expand Up @@ -393,9 +407,13 @@ def get_urls(self):
]
return urls + super().get_urls()

def automatic_date(self, obj):
"""Display automatically generated date/date range for an event as a formatted string"""
return standard_date_display(obj.documents_date_range)
def active_dates(self, obj):
"""Display automatically generated active date/date range for a person as a formatted string"""
return standard_date_display(obj.active_date_range)

def deceased_mention_dates(self, obj):
"""Display automatically generated deceased date/date range for a person as a formatted string"""
return standard_date_display(obj.deceased_date_range)

actions = (export_to_csv, merge_people)

Expand All @@ -409,22 +427,69 @@ class RoleAdmin(TabbedTranslationAdmin, admin.ModelAdmin):
ordering = ("display_label", "name")


class RelationTypeMergeAdminMixin:
@admin.display(description="Merge selected %(verbose_name_plural)s")
def merge_relation_types(self, request, queryset=None):
"""Admin action to merge selected entity-entity relation types. This
action redirects to an intermediate page, which displays a form to
review for confirmation and choose the primary type before merging.
"""
selected = request.POST.getlist("_selected_action")
if len(selected) < 2:
messages.error(
request,
"You must select at least two person-person relationships to merge",
)
return HttpResponseRedirect(
reverse("admin:entities_%s_changelist" % self.model._meta.model_name)
)
return HttpResponseRedirect(
"%s?ids=%s"
% (
reverse(f"admin:{self.merge_path_name}"),
",".join(selected),
),
status=303,
) # status code 303 means "See Other"

def get_urls(self):
"""Return admin urls; adds custom URL for merging"""
urls = [
path(
"merge/",
self.view_class.as_view(),
name=self.merge_path_name,
),
]
return urls + super().get_urls()

actions = (merge_relation_types,)


@admin.register(PersonDocumentRelationType)
class PersonDocumentRelationTypeAdmin(TabbedTranslationAdmin, admin.ModelAdmin):
class PersonDocumentRelationTypeAdmin(
RelationTypeMergeAdminMixin, TabbedTranslationAdmin, admin.ModelAdmin
):
"""Admin for managing the controlled vocabulary of people's relationships to documents"""

fields = ("name",)
search_fields = ("name",)
ordering = ("name",)
merge_path_name = "person-document-relation-type-merge"
view_class = PersonDocumentRelationTypeMerge


@admin.register(PersonPersonRelationType)
class PersonPersonRelationTypeAdmin(TabbedTranslationAdmin, admin.ModelAdmin):
class PersonPersonRelationTypeAdmin(
RelationTypeMergeAdminMixin, TabbedTranslationAdmin, admin.ModelAdmin
):
"""Admin for managing the controlled vocabulary of people's relationships to other people"""

fields = ("name", "converse_name", "category")
search_fields = ("name",)
ordering = ("name",)
merge_path_name = "person-person-relation-type-merge"
view_class = PersonPersonRelationTypeMerge


@admin.register(PersonPlaceRelationType)
Expand Down Expand Up @@ -477,14 +542,18 @@ class PlacePlaceReverseInline(admin.TabularInline):
verbose_name_plural = "Related Places (automatically populated)"
fields = (
"place_a",
"type",
"relation",
"notes",
)
fk_name = "place_b"
readonly_fields = ("place_a", "type", "notes")
readonly_fields = ("place_a", "relation", "notes")
extra = 0
max_num = 0

def relation(self, obj=None):
"""Get the relationship type's converse name, if it exists, or else the type name"""
return (obj.type.converse_name or str(obj.type)) if obj else None


class PlaceEventInline(admin.TabularInline):
"""Inline for events related to a place"""
Expand Down Expand Up @@ -595,7 +664,7 @@ def get_urls(self):
class PlacePlaceRelationTypeAdmin(TabbedTranslationAdmin, admin.ModelAdmin):
"""Admin for managing the controlled vocabulary of places' relationships to other places"""

fields = ("name",)
fields = ("name", "converse_name")
search_fields = ("name",)
ordering = ("name",)

Expand Down
Loading

0 comments on commit fd3cfa4

Please sign in to comment.