From 4ee14d95147a1642bd7e0408f9d439e87ea22a84 Mon Sep 17 00:00:00 2001 From: Kalle Westerling Date: Thu, 26 Sep 2024 09:35:11 +0200 Subject: [PATCH] Further refactoring (mostly class-based views) --- seshat/apps/constants.py | 8 + seshat/apps/core/mixins.py | 38 +++ seshat/apps/core/models.py | 463 +++++++++++++++++----------------- seshat/apps/core/urls.py | 14 +- seshat/apps/core/views.py | 161 ++++++------ seshat/apps/crisisdb/utils.py | 5 +- seshat/apps/utils.py | 31 ++- 7 files changed, 399 insertions(+), 321 deletions(-) create mode 100644 seshat/apps/core/mixins.py diff --git a/seshat/apps/constants.py b/seshat/apps/constants.py index 7f59da232..6dcde08c8 100644 --- a/seshat/apps/constants.py +++ b/seshat/apps/constants.py @@ -690,6 +690,14 @@ def _wrap(label, simple=False): "note": "No_Actual_note", "no_value": "The value is not available.", "nothing": "NOTHING", + "private_comments": "NO_Private_COMMENTS_TO_SHOW", + "titles": "NO_TITLES_PROVIDED", + "ref_no_title": "REFERENCE_WITH_NO_TITLE", + "ref_no_long_name": "REFERENCE_WITH_NO_LONG_NAME", + "bad_reference": "BADBADREFERENCE", + "comment_parts": " Nothing ", + "descriptions": "No descriptions.", + "comment": "EMPTY_COMMENT", } ) diff --git a/seshat/apps/core/mixins.py b/seshat/apps/core/mixins.py new file mode 100644 index 000000000..64bbe0e2a --- /dev/null +++ b/seshat/apps/core/mixins.py @@ -0,0 +1,38 @@ +from django.urls import reverse +from ..utils import deprecated + + +class ZoteroMixIn: + @property + def __no_zotero_link__(self) -> str: + return reverse("citation-update", args=[self.id]) + + @property + def has_zotero(self) -> bool: + return self.ref.has_zotero + + @property + def zotero_link(self) -> str: + """ + Returns the Zotero link for the citation. + + Returns: + str: The Zotero link for the citation. + """ + if self.has_zotero: + return self.ref.full_zotero_link + + return self.__no_zotero_link__ + + @deprecated + def zoteroer(self) -> str: + """ + For backward compatibility, this method returns the Zotero link for the citation. + + Returns: + str: The Zotero link for the citation. + + Deprecated: + Use the `zotero_link` property instead. + """ + return self.zotero_link diff --git a/seshat/apps/core/models.py b/seshat/apps/core/models.py index b55e94f04..23d5f24b4 100644 --- a/seshat/apps/core/models.py +++ b/seshat/apps/core/models.py @@ -1,4 +1,5 @@ import uuid +import warnings from django.contrib.gis.db import models from django.core.exceptions import ValidationError @@ -8,14 +9,15 @@ from django.urls import reverse from ..accounts.models import Seshat_Expert -from ..utils import get_color, get_date, ATTRS_HTML -from ..constants import ZOTERO +from ..utils import get_color, get_date, deprecated, ATTRS_HTML +from ..constants import NO_DATA, ZOTERO from .constants import ( POLITY_TAG_CHOICES, WORLD_REGION_CHOICES, TAGS, ) +from .mixins import ZoteroMixIn class SeshatPrivateComment(models.Model): @@ -126,7 +128,7 @@ def __str__(self) -> str: if self.private_comment_part_text: return self.private_comment_part_text - return "NO_Private_COMMENTS_TO_SHOW" + return NO_DATA.private_comments class Macro_region(models.Model): @@ -537,22 +539,39 @@ class Reference(models.Model): ) modified_date = models.DateTimeField(auto_now=True, blank=True, null=True) - def __str__(self) -> str: - original_title = self.title + def get_reference_string(self): + if not self.year: + return f"{self.creator}_XXXX" + + return f"{self.creator}_{self.year}" + + def get_short_title(self) -> str: + if not self.long_name: + return "BlaBla" - if len(original_title) > 60: - last_word = original_title[60:].split(" ")[0] - shorter_title = f"{original_title[0:60]} {last_word} ..." - else: - shorter_title = original_title + if len(self.long_name) < 60: + return self.long_name - if self.year: - return f"({self.creator}_{self.year}): {shorter_title}" + first_part, last_word = ( + self.long_name[0:60], + self.long_name[60:].split(" ")[0], + ) - return f"({self.creator}_XXXX): {shorter_title}" + return f"{first_part} {last_word} ..." @property - def reference_short_title(self) -> str: + def full_zotero_link(self) -> str: + if not self.has_zotero: + return "" + + return f"{ZOTERO.BASEURL}{self.zotero_link}" + + @property + def has_zotero(self) -> bool: + return self.zotero_link and "NOZOTERO_LINK" not in self.zotero_link + + @property + def short_title(self) -> str: """ Returns a short title for the reference. If the title is longer than 60 characters, it is truncated. If the title is not provided, a default @@ -561,26 +580,17 @@ def reference_short_title(self) -> str: Returns: str: A short title for the reference. """ - shorter_name = "" - - # Set shorter_name to the first 60 characters of the long_name - if self.long_name and len(self.long_name) > 60: - last_word = self.long_name[60:].split(" ")[0] - shorter_name = f"{self.long_name[0:60]} {last_word} ..." - elif self.long_name: - shorter_name = self.long_name - else: - shorter_name = "BlaBla" + shorter_name = self.get_short_title() # If the zotero link is not provided, return a default title - if self.zotero_link and "NOZOTERO_LINK" in self.zotero_link: + if not self.has_zotero: return f"(NOZOTERO_REF: {shorter_name})" - # If there is a title, return the title - if self.title: - return self.title + # If there is no title, return the title + if not self.title: + return NO_DATA.title - return "NO_TITLES_PROVIDED" + return shorter_name def get_absolute_url(self) -> str: """ @@ -593,6 +603,24 @@ def get_absolute_url(self) -> str: """ return reverse("references") + @property + def reference_short_title(self) -> str: + """ + Property kept here for backward compatibility. Use the short_title property instead. + """ + warnings.simplefilter("always", DeprecationWarning) # turn off filter + warnings.warn( + "This property is deprecated - use short_title instead", + category=DeprecationWarning, + stacklevel=2, + ) + warnings.simplefilter("default", DeprecationWarning) # reset filter + + return self.short_title + + def __str__(self) -> str: + return f"({self.get_reference_string()}): {self.get_short_title()}" + class Meta: """ :noindex: @@ -602,7 +630,7 @@ class Meta: ordering = ["-created_date", "title"] -class Citation(models.Model): +class Citation(ZoteroMixIn, models.Model): """ Model representing a specific citation. """ @@ -625,73 +653,78 @@ class Citation(models.Model): ) modified_date = models.DateTimeField(auto_now=True, blank=True, null=True) - def zoteroer(self) -> str: + def get_title(self) -> str: + """ + # TODO: docstring """ - Returns the Zotero link for the citation. + if not self.ref or (self.ref and not self.ref.title): + return NO_DATA.ref_no_title - Returns: - str: The Zotero link for the citation. + return self.ref.title + + def get_short_title(self, cutoff=50) -> str: """ - if ( - self.ref.zotero_link - and "NOZOTERO_LINK" not in self.ref.zotero_link - ): - return f"{ZOTERO.BASEURL}{self.ref.zotero_link}" + # TODO: docstring + """ + title = self.get_title() - return reverse("citation-update", args=[self.id]) + if not title: + return "BlaBlaBla" - def __str__(self) -> str: - if self.ref and self.ref.title: - original_title = self.ref.title - else: - original_title = "REFERENCE_WITH_NO_TITLE" - - if original_title and len(original_title) > 50: - last_word = original_title[50:].split(" ")[0] - short_title = f"{original_title[0:50]} {last_word}..." - elif original_title: - short_title = original_title - else: - short_title = "BlaBlaBla" - - if self.ref and self.ref.long_name: - self.ref.long_name = self.ref.long_name - else: - self.ref.long_name = "REFERENCE_WITH_NO_LONG_NAME" - if self.ref.long_name and len(self.ref.long_name) > 50: - last_word = self.ref.long_name[50:].split(" ")[0] - shorter_name = f"{self.ref.long_name[0:50]} {last_word}..." - elif self.ref.long_name: - shorter_name = self.ref.long_name - else: - shorter_name = "BlaBla" + if len(title) < cutoff: + return title - if ( - self.ref - and self.ref.zotero_link - and "NOZOTERO_LINK" in self.ref.zotero_link - ): - return f"(NOZOTERO: {shorter_name})" - - if self.ref and self.ref.creator: - if self.page_from is None and self.page_to is None: - return f"({self.ref.creator} {self.ref.year}): {short_title}" - elif self.page_from == self.page_to or ( - (not self.page_to) and self.page_from - ): - return f"({self.ref.creator} {self.ref.year}, p. {self.page_from}): {short_title}" # noqa: E501 pylint: disable=C0301 - elif self.page_from == self.page_to or ( - (not self.page_from) and self.page_to - ): - return f"({self.ref.creator} {self.ref.year}, p. {self.page_to}): {short_title}" # noqa: E501 pylint: disable=C0301 - elif self.page_from and self.page_to: - return f"({self.ref.creator} {self.ref.year}, pp. {self.page_from}-{self.page_to}): {short_title}" # noqa: E501 pylint: disable=C0301 - else: - return "({self.ref.creator} {self.ref.year}): {shorter_title}" - else: - return "BADBADREFERENCE" + first_part, last_word = title[0:cutoff], title[cutoff:].split(" ")[0] - def full_citation_display(self) -> str: + return f"{first_part} {last_word}..." + + def get_name(self): + """ + # TODO: docstring + """ + if not self.ref or (self.ref and not self.ref.long_name): + return NO_DATA.ref_no_long_name + + return self.ref.long_name + + def get_short_name(self): + """ + # TODO: docstring + """ + name = self.get_name() + + if not name: + return "BlaBlaBla" + + if len(name) < 50: + return name + + first_part, last_word = name[0:50], name[50:].split(" ")[0] + + return f"{first_part} {last_word}..." + + def get_page_range(self): + """ + # TODO: docstring + """ + if not self.page_from and not self.page_to: + return "" # TODO: Should this case be handled differently? + + if not self.page_from and self.page_to: + return f"p. {self.page_to}" + + if self.page_from == self.page_to or not self.page_to: + return f"p. {self.page_from}" + + return f"pp. {self.page_from}-{self.page_to}" + + def get_reference_string(self): + """ + # TODO: docstring + """ + return f"{self.ref.creator} {self.ref.year}" + + def get_citation_display(self, include_html=False): """ Returns a string of the full citation. If the citation has a title, it is included in the string. If the citation has a creator, it is @@ -699,85 +732,41 @@ def full_citation_display(self) -> str: the string. If the citation has a page_from, it is included in the string. If the citation has a page_to, it is included in the string. + Args: + include_html # TODO + Returns: str: A string of the full citation. """ - if self.ref and self.ref.title: - original_title = self.ref.title - else: - original_title = "REFERENCE_WITH_NO_TITLE" - - if original_title: - shorter_title = original_title - else: - shorter_title = "BlaBlaBla" - - if self.ref and self.ref.long_name: - self.ref.long_name = self.ref.long_name - else: - self.ref.long_name = "REFERENCE_WITH_NO_LONG_NAME" - - if self.ref.long_name: - shorter_name = self.ref.long_name - else: - shorter_name = "BlaBla" + # First, check whether we have a Zotero reference - if not, return + if not self.has_zotero: + return f"(NOZOTERO: {self.get_short_name()})" - if ( - self.ref - and self.ref.zotero_link - and "NOZOTERO_LINK" in self.ref.zotero_link - ): - return f"(NOZOTERO: {shorter_name})" - - if self.ref and self.ref.creator: - if self.page_from is None and self.page_to is None: - return f"({self.ref.creator} {self.ref.year}): {shorter_title}" # noqa: E501 pylint: disable=C0301 + # Check if we have a reference at all + if not self.ref or (self.ref and not self.ref.creator): + return NO_DATA.bad_reference - if self.page_from == self.page_to or ( - (not self.page_to) and self.page_from - ): - return f"({self.ref.creator} {self.ref.year}, p. {self.page_from}): {shorter_title}" # noqa: E501 pylint: disable=C0301 + # Start putting together the reference + reference_string = self.get_reference_string() + short_title = self.get_short_title() + page_range = self.get_page_range() - if self.page_from == self.page_to or ( - (not self.page_from) and self.page_to - ): - return f"({self.ref.creator} {self.ref.year}, p. {self.page_to}): {shorter_title}" # noqa: E501 pylint: disable=C0301 + if include_html: + if not page_range: + return f"({reference_string}): {short_title}" - if self.page_from and self.page_to: - return f"({self.ref.creator} {self.ref.year}, pp. {self.page_from}-{self.page_to}): {shorter_title}" # noqa: E501 pylint: disable=C0301 + return f"({reference_string}, {page_range}): {short_title}" # noqa: E501 - return f"({self.ref.creator} {self.ref.year}): {shorter_title}" # noqa: E501 pylint: disable=C0301 + if not page_range: + return f"({reference_string}): {short_title}" - return "BADBADREFERENCE" + return f"({reference_string}, {page_range}): {short_title}" - class Meta: - """ - :noindex: - """ - - ordering = ["-modified_date"] - constraints = [ - models.UniqueConstraint( - name="No_PAGE_TO_AND_FROM", - fields=("ref",), - condition=( - Q(page_to__isnull=True) & Q(page_from__isnull=True) - ), - ), - models.UniqueConstraint( - name="No_PAGE_TO", - fields=("ref", "page_from"), - condition=Q(page_to__isnull=True), - ), - models.UniqueConstraint( - name="No_PAGE_FROM", - fields=("ref", "page_to"), - condition=Q(page_from__isnull=True), - ), - ] + def full_citation_display(self) -> str: + return self.get_citation_display(include_html=True) @property - def citation_short_title(self) -> str: + def short_title(self) -> str: """ Returns a short title for the citation. If the title is longer than 40 characters, it is truncated. If the title is not provided, a default @@ -786,39 +775,32 @@ def citation_short_title(self) -> str: Returns: str: A short title for the citation. """ - if self.ref.long_name and len(self.ref.long_name) > 40: - shorter_name = ( - self.ref.long_name[0:40] - + self.ref.long_name[40:].split(" ")[0] - + "..." - ) - elif self.ref.long_name: - shorter_name = self.ref.long_name - else: - # TODO: Does this ever happen? If so, should we handle it differently? - # (shorter_name = "BlaBla" doesn't seem helpful) - shorter_name = "BlaBla" + if not self.has_zotero: + return f"(NOZOTERO: {self.get_short_title(cutoff=40)})" - if "NOZOTERO_LINK" in self.ref.zotero_link: - return f"(NOZOTERO: {shorter_name})" + reference_string = self.get_reference_string() if self.page_from is None and self.page_to is None: - return f"[{self.ref.creator} {self.ref.year}]" + return f"[{reference_string}]" - if self.page_from == self.page_to or ( - (not self.page_to) and self.page_from - ): - return f"[{self.ref.creator} {self.ref.year}, p. {self.page_from}]" + page_range = self.get_page_range() - if self.page_from == self.page_to or ( - (not self.page_from) and self.page_to - ): - return f"[{self.ref.creator} {self.ref.year}, p. {self.page_to}]" + if not page_range: + return f"[{reference_string}]" - if self.page_from and self.page_to: - return f"[{self.ref.creator} {self.ref.year}, pp. {self.page_from}-{self.page_to}]" # noqa: E501 pylint: disable=C0301 + return f"[{reference_string}, {page_range}]" - return f"[{self.ref.creator} {self.ref.year}]" + @property + def citation_short_title(self) -> str: + warnings.simplefilter("always", DeprecationWarning) # turn off filter + warnings.warn( + "This property is deprecated - use short_title instead", + category=DeprecationWarning, + stacklevel=2, + ) + warnings.simplefilter("default", DeprecationWarning) # reset filter + + return self.short_title def get_absolute_url(self) -> str: """ @@ -848,7 +830,37 @@ def save(self, *args, **kwargs): try: super(Citation, self).save(*args, **kwargs) except IntegrityError as e: - print(e) # TODO: We probably want to handle this differently + # TODO: We will want to handle this differently + print(e) + + def __str__(self) -> str: + return self.get_citation_display(include_html=False) + + class Meta: + """ + :noindex: + """ + + ordering = ["-modified_date"] + constraints = [ + models.UniqueConstraint( + name="No_PAGE_TO_AND_FROM", + fields=("ref",), + condition=( + Q(page_to__isnull=True) & Q(page_from__isnull=True) + ), + ), + models.UniqueConstraint( + name="No_PAGE_TO", + fields=("ref", "page_from"), + condition=Q(page_to__isnull=True), + ), + models.UniqueConstraint( + name="No_PAGE_FROM", + fields=("ref", "page_to"), + condition=Q(page_from__isnull=True), + ), + ] class SeshatComment(models.Model): @@ -861,30 +873,37 @@ class SeshatComment(models.Model): null=True, ) - def zoteroer(self): + @property + def __no_zotero_link__(self) -> str: + return "#" + + def __str__(self) -> str: + comment_parts = self.get_comment_parts() + + if not comment_parts: + return NO_DATA.comment_parts + + return " ".join(comment_parts) + + def get_absolute_url(self) -> str: """ - Returns the Zotero link for the comment. If the comment has a Zotero - link, it is returned. Otherwise, a default link (#) is returned. + Returns the url to access a particular instance of the model. + + :noindex: Returns: - str: The Zotero link for the comment. + str: A string of the url to access a particular instance of the model. """ - if ( - self.ref.zotero_link - and "NOZOTERO_LINK" not in self.ref.zotero_link - ): - return f"{ZOTERO.BASEURL}{self.ref.zotero_link}" - - return "#" + return reverse("seshatcomments") - def __str__(self) -> str: + def get_comment_parts(self): inner_comments = self.inner_comments_related.all() if not inner_comments and self.text: - return "No descriptions." + return NO_DATA.descriptions if not inner_comments: - return "EMPTY_COMMENT" + return NO_DATA.comment comment_parts = [] for comment in inner_comments.order_by("comment_order"): @@ -896,7 +915,11 @@ def __str__(self) -> str: ) text = f"{before}{comment.display_citations_plus} {after}" else: - text = comment.comment_part_text if comment.comment_part_text else "" + text = ( + comment.comment_part_text + if comment.comment_part_text + else "" + ) text = text.strip("
") # drop any leading
tags if comment.display_citations_plus: @@ -907,21 +930,7 @@ def __str__(self) -> str: # Drop empty strings comment_parts = [x for x in comment_parts if x] - if not comment_parts: - return " Nothing " - - return " ".join(comment_parts) - - def get_absolute_url(self) -> str: - """ - Returns the url to access a particular instance of the model. - - :noindex: - - Returns: - str: A string of the url to access a particular instance of the model. - """ - return reverse("seshatcomments") + return comment_parts class SeshatCommentPart(models.Model): @@ -984,7 +993,7 @@ def get_citations_plus_for_comments(self): return ", ".join( [ - f' {x.citation.citation_short_title}' # noqa: E501 pylint: disable=C0301 + f' {x.citation.short_title}' for x in scp_tr ] ) @@ -1002,15 +1011,15 @@ def display_citations(self): Returns: str: The citations of the model instance, separated by comma. """ - if self.comment_citations.all(): - return ", ".join( - [ - f' {citation.citation_short_title}' - for citation in self.comment_citations.all() - ] - ) + if not self.comment_citations.all(): + return "" - return "" + return ", ".join( + [ + f' {citation.short_title}' + for citation in self.comment_citations.all() + ] + ) @property def display_citations_plus(self): @@ -1065,16 +1074,16 @@ class Meta: ordering = ["comment_order", "modified_date"] def __str__(self) -> str: + if not self.comment_part_text: + return "NO_SUB_COMMENTS_TO_SHOW" + if self.comment_part_text and self.display_citations_plus: return f"{self.comment_part_text} {self.display_citations_plus}" if self.comment_part_text and self.display_citations: return f"{self.comment_part_text} {self.display_citations}" - if self.comment_part_text: - return self.comment_part_text - - return "NO_SUB_COMMENTS_TO_SHOW" + return self.comment_part_text class ScpThroughCtn(models.Model): diff --git a/seshat/apps/core/urls.py b/seshat/apps/core/urls.py index 3eba3ee34..47b4439e8 100644 --- a/seshat/apps/core/urls.py +++ b/seshat/apps/core/urls.py @@ -162,10 +162,10 @@ downloads.capital_download_view, name="capital-download", ), - path("search/", views.search_view, name="search"), + path("search/", views.SearchRedirectView.as_view(), name="search"), path( "search_suggestions/", - views.search_suggestions_view, + views.SearchSuggestionsView.as_view(), name="search_suggestions", # noqa: E501 TODO: The search_suggestions_view is not used anywhere in the codebase ), path("signup/", views.signup_traditional_view, name="signup"), @@ -345,20 +345,20 @@ views.nlp_datapoints_2_view, name="nlp_datapoints_2", ), - # TODO: Correct? The below is commented out as it is not used in the codebase - # path("core/not_found_404", views.NotFoundView.as_view(), name="four-o-four"), path("core/world_map/", views.world_map_view, name="world_map"), path( "core/world_map_one_year/", - views.world_map_one_year_view, + views.WorldmapView.as_view(one_year=True), name="world_map_one_year", ), path( - "core/world_map_all/", views.world_map_all_view, name="world_map_all" + "core/world_map_all/", + views.WorldmapView.as_view(), + name="world_map_all", ), path( "core/provinces_and_countries", - views.provinces_and_countries_view, + views.ProvincesCountriesView.as_view(), name="provinces_and_countries", ), ] diff --git a/seshat/apps/core/views.py b/seshat/apps/core/views.py index a27b2906d..98846c42c 100644 --- a/seshat/apps/core/views.py +++ b/seshat/apps/core/views.py @@ -22,12 +22,14 @@ from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.views.decorators.http import require_GET from django.views.generic import ( + View, ListView, CreateView, UpdateView, DeleteView, DetailView, TemplateView, + RedirectView, ) from django.views.generic.edit import FormMixin @@ -2923,47 +2925,65 @@ def world_map_one_year_view(request): return JsonResponse(content) -# TODO: rewrite as a class-based view (TemplateView/View) -def world_map_all_view(request): - """ - This view is used to display a map with polities plotted on it. The view - loads all polities for the range of years. +class WorldmapView(View): + one_year = False - Args: - request: The request object. + def get(self, request, *args, **kwargs): + """ + This view is used to display a map with polities plotted on it. The view + loads all polities for the range of years. - Returns: - JsonResponse: The HTTP response with serialized JSON. - """ + Returns: + JsonResponse: The HTTP response with serialized JSON. + """ + if self.one_year: + year = request.GET.get("year", WORLD_MAP_INITIAL_DISPLAYED_YEAR) + data = get_polity_shape_content(displayed_year=year) + else: + # Temporary restriction on the latest year for the whole map view + data = get_polity_shape_content(override_latest_year=2014) - # Temporary restriction on the latest year for the whole map view - content = get_polity_shape_content(override_latest_year=2014) + data = common_map_view_content(data) - content = common_map_view_content(content) + return JsonResponse(data) - return JsonResponse(content) + @classmethod + def as_view( + cls, + one_year=None, + **initkwargs, + ): + """ + # TODO: docstring + Returns: + A callable view. + """ + initkwargs = { + "one_year": one_year, + } -# TODO: rewrite as a class-based view (TemplateView/View) -def provinces_and_countries_view(request): - """ - This view is used to get the provinces and countries for the map. + return super().as_view(**initkwargs) - Args: - request: The request object. - Returns: - JsonResponse: The HTTP response with serialized JSON. - """ - provinces = get_provinces() - countries = get_provinces(selected_base_map_gadm="country") +class ProvincesCountriesView(View): + def get(self, request, *args, **kwargs): + """ + This view is used to get the provinces and countries for the map as JSON data. - content = { - "provinces": provinces, - "countries": countries, - } + Returns: + JsonResponse: The HTTP response with serialized JSON. + """ + provinces = get_provinces() + countries = get_provinces(selected_base_map_gadm="country") - return JsonResponse(content) + data = { + "provinces": provinces, + "countries": countries, + } + + # Return dropdown template as JSON response + return JsonResponse(data) # TODO: rewrite as a class-based view (TemplateView/View) @@ -3158,7 +3178,7 @@ def update_seshat_comment_part_view(request, pk): ) -# TODO: rewrite as a class-based view (RedirectView) +# TODO: rewrite as a class-based view (CreateView) @login_required def create_subcomment_new_view(request, app_name, model_name, instance_id): """ @@ -3327,7 +3347,7 @@ def create_subcomment_newer_view(request, app_name, model_name, instance_id): return render(request, "core/seshatcomments/your_template.html", context) -# TODO: rewrite as a class-based view (RedirectView) +# TODO: rewrite as a class-based view (CreateView) @login_required @permission_required("core.add_seshatprivatecommentpart") def create_private_subcomment_new_view( @@ -3495,63 +3515,40 @@ def seshatcomments_create3_view(request): ) -# TODO: rewrite as a class-based view (RedirectView? View?) -def search_view(request): - """ - View to search for a polity. - - Note: - This view can handle GET requests. - - Args: - request: The request object. +class SearchSuggestionsView(TemplateView): + template_name = "core/partials/_search_suggestions.html" - Returns: - HttpResponse: The HTTP response. - """ - search_term = request.GET.get("search", "") - - if search_term: - try: - polity = Polity.objects.filter(name__icontains=search_term).first() + def get_context_data(self, **kwargs) -> dict: + context = super().get_context_data(**kwargs) - if polity: - return redirect("polity-detail-main", pk=polity.pk) - else: - # No polity found = redirect to home - return redirect("index") - except Polity.DoesNotExist: - # Handle the case where no polity matches the search term - pass + search_term = self.request.GET.get("search", "") + _filter = ( + Q(name__icontains=search_term) + | Q(long_name__icontains=search_term) + | Q(new_name__icontains=search_term) + ) - # Redirect to home if no search term is provided or no match is found - return redirect("index") + context = dict(context, **{ + "polities": Polity.objects.filter(_filter).order_by("start_year")[:8] + }) + return context -# TODO: rewrite as a class-based view (TemplateView/View) -def search_suggestions_view(request): - """ - View to get search suggestions for a polity. - Note: - This view can handle GET requests. +class SearchRedirectView(RedirectView): + pattern_name = "polity-detail-main" - Args: - request: The request object. + def get_redirect_url(self, *args, **kwargs): + search_term = self.request.GET.get("search", "") - Returns: - HttpResponse: The HTTP response. - """ - search_term = request.GET.get("search", "") - _filter = ( - Q(name__icontains=search_term) - | Q(long_name__icontains=search_term) - | Q(new_name__icontains=search_term) - ) + polity = Polity.objects.filter( + name__icontains=search_term + ).first() - # TODO? Limit to 5 suggestions [:5] - context = { - "polities": Polity.objects.filter(_filter).order_by("start_year") - } + if not polity: + return reverse("index") - return render(request, "core/partials/_search_suggestions.html", context) + return reverse( + self.pattern_name, + kwargs={"pk": polity.pk}, + ) diff --git a/seshat/apps/crisisdb/utils.py b/seshat/apps/crisisdb/utils.py index 12720f4a2..dac012af2 100644 --- a/seshat/apps/crisisdb/utils.py +++ b/seshat/apps/crisisdb/utils.py @@ -3,9 +3,8 @@ "expand_context_from_variable_hierarchy", ] -from ..constants import ICONS +from ..constants import ICONS, NO_DATA from ..core.models import Variablehierarchy -from ..constants import NO_DATA def return_citations(cls): @@ -29,7 +28,7 @@ def return_citations(cls): """ return "
".join( [ - f'{ICONS.book} {citation.full_citation_display()}' # noqa: E501 pylint: disable=C0301 + f'{ICONS.book} {citation.full_citation_display()}' # noqa: E501 pylint: disable=C0301 for citation in cls.citations.all() ] ) diff --git a/seshat/apps/utils.py b/seshat/apps/utils.py index 70017e046..37cd8daef 100644 --- a/seshat/apps/utils.py +++ b/seshat/apps/utils.py @@ -30,7 +30,10 @@ import csv import datetime +import functools import os +import warnings + from typing import Union import requests @@ -201,7 +204,7 @@ def return_citations( return join_str.join( [ - f'{custom_func(citation) if isinstance(custom_func, callable) else citation}""' # noqa: E501 pylint: disable=C0301 + f'{custom_func(citation) if isinstance(custom_func, callable) else citation}""' # noqa: E501 pylint: disable=C0301 for citation in cls.citations.all()[:limit] ] ) @@ -902,7 +905,12 @@ def get_variable_context( subsection = str(model().subsection()) subsubsection = str(model().subsubsection()) - subsection_str = subsection.replace("-", "_").replace(" ", "_").replace(":", "").lower() # noqa: E501 + subsection_str = ( + subsection.replace("-", "_") + .replace(" ", "_") + .replace(":", "") + .lower() + ) # noqa: E501 section_download_names[subsection] = f"{prefix}{subsection_str}" @@ -947,3 +955,22 @@ def get_variable_context( "number_of_all_rows": row_count, "number_of_variables": variable_count, } + + +def deprecated(func): + """This is a decorator which can be used to mark functions + as deprecated. It will result in a warning being emitted + when the function is used.""" + + @functools.wraps(func) + def new_func(*args, **kwargs): + warnings.simplefilter("always", DeprecationWarning) # turn off filter + warnings.warn( + f"Call to deprecated function {func.__name__}.", + category=DeprecationWarning, + stacklevel=2, + ) + warnings.simplefilter("default", DeprecationWarning) # reset filter + return func(*args, **kwargs) + + return new_func