-
Notifications
You must be signed in to change notification settings - Fork 10
Corrige coleta journal logo #1365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
01ae5d1
3e2db1c
095983c
c7f4251
5ce2e0c
7045a96
df97543
e3dbf78
b7b2ea7
8f26636
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,4 @@ | ||
| from rest_framework import serializers | ||
| from wagtail.models.sites import Site | ||
|
|
||
| from core.api.v1.serializers import LanguageSerializer | ||
| from journal import models | ||
|
|
@@ -196,11 +195,11 @@ def get_title_in_database(self, obj): | |
| return title_in_db | ||
|
|
||
| def get_url_logo(self, obj): | ||
| if obj.logo: | ||
| domain = Site.objects.get(is_default_site=True).hostname | ||
| domain = f"http://{domain}" | ||
| return f"{domain}{obj.logo.file.url}" | ||
| return None | ||
| try: | ||
| request = self.context.get('request') | ||
| return obj.get_url_logo(request.build_absolute_uri('/') if request else None) | ||
| except Exception: | ||
| return obj.get_url_logo() | ||
|
Comment on lines
197
to
+202
|
||
|
|
||
| def get_email(self, obj): | ||
| if obj.journal_email.all(): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # Generated by Django 5.2.7 on 2026-02-26 21:01 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ("journal", "0055_journalproxyadminonly_and_more"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.DeleteModel( | ||
| name="JournalLogo", | ||
| ), | ||
| ] | ||
|
Comment on lines
+1
to
+16
|
||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |||
| from wagtail.fields import RichTextField | ||||
| from wagtail.models import Orderable | ||||
| from wagtailautocomplete.edit_handlers import AutocompletePanel | ||||
| from wagtail.models.sites import Site | ||||
|
|
||||
| from collection.models import Collection | ||||
| from core.choices import MONTHS, LICENSE_TYPES | ||||
|
|
@@ -796,6 +797,19 @@ def journal_acrons(self): | |||
| .filter(collection__is_active=True) | ||||
| .values_list("collection__acron3", flat=True) | ||||
| ) | ||||
|
|
||||
|
||||
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When root_url is provided and contains a trailing slash (as request.build_absolute_uri('/') does), and rendition.url starts with a slash (which is typical for Django media URLs), this will result in a double slash in the URL (e.g., http://example.com//media/...). Consider stripping trailing slashes from root_url before concatenation, or use urljoin for proper URL construction.
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -33,7 +33,6 @@ | |||
| WebOfKnowledge, | ||||
| WebOfKnowledgeSubjectCategory, | ||||
| TitleInDatabase, | ||||
| JournalLogo, | ||||
| JournalOtherTitle, | ||||
| JournalLicense, | ||||
| ) | ||||
|
|
@@ -265,12 +264,15 @@ def update_logo( | |||
| journal, | ||||
| ): | ||||
| try: | ||||
| if journal_logo := JournalLogo.objects.filter(journal=journal).first(): | ||||
| journal.logo = journal_logo.logo | ||||
| else: | ||||
| tasks.fetch_and_process_journal_logo.apply_async( | ||||
| kwargs=dict(journal_id=journal.id) | ||||
| ) | ||||
| if not journal: | ||||
| return None | ||||
|
|
||||
|
||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,8 @@ | ||||||||
| import logging | ||||||||
| import sys | ||||||||
| from io import BytesIO | ||||||||
|
||||||||
|
|
||||||||
| from PIL import Image as PilImage | ||||||||
| from celery import group | ||||||||
| from django.contrib.auth import get_user_model | ||||||||
| from django.core.files.base import ContentFile | ||||||||
|
|
@@ -16,7 +18,6 @@ | |||||||
| AMJournal, | ||||||||
| Journal, | ||||||||
| JournalLicense, | ||||||||
| JournalLogo, | ||||||||
| SciELOJournal, | ||||||||
| ) | ||||||||
| from journal.sources import classic_website | ||||||||
|
Comment on lines
18
to
23
|
||||||||
|
|
@@ -119,18 +120,22 @@ def _normalize_collection_domain(url, strip_www=False): | |||||||
|
|
||||||||
| def _build_logo_url(collection, journal_acron): | ||||||||
| """Build logo URL based on collection type.""" | ||||||||
| try: | ||||||||
| domain = _normalize_collection_domain(collection.domain) | ||||||||
| except Exception as e: | ||||||||
| logging.error(f"Error normalizing collection domain: {e}") | ||||||||
| # collection.domain contém https:// ou http:// | ||||||||
| if not collection.domain: | ||||||||
| logger.warning(f"Collection {collection.acron3} has no domain defined") | ||||||||
| return None | ||||||||
| if not journal_acron: | ||||||||
| logger.warning(f"Journal with collection {collection.acron3} has no acronym defined") | ||||||||
| return None | ||||||||
| domain = collection.domain | ||||||||
| if not domain: | ||||||||
| logger.warning(f"Collection {collection.acron3} has no domain defined") | ||||||||
| return None | ||||||||
|
Comment on lines
+131
to
133
|
||||||||
| if not domain: | |
| logger.warning(f"Collection {collection.acron3} has no domain defined") | |
| return None |
Copilot
AI
Feb 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code directly uses 'collection.domain' without checking if it's None. Since the Collection model defines domain as 'null=True', this could result in constructing URLs like "None/media/images/..." if a collection has no domain set. Consider adding a null check: 'if not collection.domain: return None' before using it.
Copilot
AI
Feb 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment states "collection.domain contém https:// ou http://" (contains https:// or http://), but the Collection model uses URLField which doesn't enforce this, and test data shows domains stored without protocol (e.g., "www.scielo.br" in journal/tests.py:190,196). This inconsistency could lead to malformed URLs like "www.scielo.br/media/images/..." instead of "http://www.scielo.br/media/images/...". Consider either: 1) Adding validation to ensure domain always includes protocol, 2) Using urlparse to normalize the domain, or 3) Explicitly prepending the protocol based on collection type.
Copilot
AI
Feb 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The EXT_MAP dictionary is defined inside the function and recreated on every function call. Since this is a Celery task that could be called many times, consider moving this constant to module level (above the function) to avoid unnecessary recreation. This is a minor performance optimization.
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The EXT_MAP dictionary is defined inside the function body, which means it gets recreated on every function call. Since this is a constant mapping that doesn't change, it should be defined at the module level (outside the function) for better performance and to follow Python conventions for constants. Consider moving it to the top of the file after the logger definition.
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's trailing whitespace on this line (after the closing brace on line 182 or after the except clause). This should be removed to maintain code cleanliness and follow PEP 8 style guidelines.
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's trailing whitespace on this line. This should be removed to maintain code cleanliness and follow PEP 8 style guidelines.
Copilot
AI
Feb 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The get_or_create logic using 'title' as the lookup field means that if an Image with that journal_acron title already exists, it won't be updated even if the logo file has changed. The 'defaults' parameter only applies when creating a new record. If logos can change over time, this should either use update_or_create, or delete the old image before creating a new one, or use a different unique identifier that includes versioning information.
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Image.objects.get_or_create() call uses only title as the lookup field, which means if an image with this journal_acron already exists, it will be reused without updating its file. This could be problematic if:
- The logo image changes but keeps the same journal acronym
- An old logo has the wrong extension (e.g., .gif) and you want to replace it with the corrected extension
Consider using update_or_create() instead, or add logic to delete/update the existing image when needed, especially since this PR is specifically about fixing incorrect extensions on existing logos.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -258,43 +258,43 @@ def test_load_journal_scl_from_article_meta(self): | |||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| class TestFetchAndProcessJournalLogosInCollection(TestCase): | ||||||||||||||||||||||||||||||||||||||||||||||
| def setUp(self): | ||||||||||||||||||||||||||||||||||||||||||||||
| self.user = User.objects.create(username="teste", password="teste") | ||||||||||||||||||||||||||||||||||||||||||||||
| self.collection = Collection.objects.create( | ||||||||||||||||||||||||||||||||||||||||||||||
| creator=self.user, acron3="scl", is_active=True | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
| self.journal = Journal.objects.create(creator=self.user, title="Test Journal") | ||||||||||||||||||||||||||||||||||||||||||||||
| self.scielo_journal = SciELOJournal.objects.create( | ||||||||||||||||||||||||||||||||||||||||||||||
| issn_scielo="1516-635X", | ||||||||||||||||||||||||||||||||||||||||||||||
| collection=self.collection, | ||||||||||||||||||||||||||||||||||||||||||||||
| journal=self.journal, | ||||||||||||||||||||||||||||||||||||||||||||||
| journal_acron="abdc", | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def test_fetch_and_process_journal_logos_with_invalid_collection(self): | ||||||||||||||||||||||||||||||||||||||||||||||
| """Test that ValueError is raised when collection does not exist""" | ||||||||||||||||||||||||||||||||||||||||||||||
| with self.assertRaises(ValueError) as context: | ||||||||||||||||||||||||||||||||||||||||||||||
| fetch_and_process_journal_logos_in_collection( | ||||||||||||||||||||||||||||||||||||||||||||||
| collection_acron3="invalid_acron", | ||||||||||||||||||||||||||||||||||||||||||||||
| username=self.user.username, | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
| self.assertIn("does not exist", str(context.exception)) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @patch("journal.tasks.group") | ||||||||||||||||||||||||||||||||||||||||||||||
| def test_fetch_and_process_journal_logos_with_valid_collection(self, mock_group): | ||||||||||||||||||||||||||||||||||||||||||||||
| """Test that task executes with valid collection""" | ||||||||||||||||||||||||||||||||||||||||||||||
| # Mock the group to avoid actually running the celery tasks | ||||||||||||||||||||||||||||||||||||||||||||||
| mock_group.return_value.return_value = None | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| result = fetch_and_process_journal_logos_in_collection( | ||||||||||||||||||||||||||||||||||||||||||||||
| collection_acron3=self.collection.acron3, | ||||||||||||||||||||||||||||||||||||||||||||||
| username=self.user.username, | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # The task should complete without raising an exception | ||||||||||||||||||||||||||||||||||||||||||||||
| # and should call group with the task signatures | ||||||||||||||||||||||||||||||||||||||||||||||
| self.assertTrue(mock_group.called) | ||||||||||||||||||||||||||||||||||||||||||||||
| # class TestFetchAndProcessJournalLogosInCollection(TestCase): | ||||||||||||||||||||||||||||||||||||||||||||||
| # def setUp(self): | ||||||||||||||||||||||||||||||||||||||||||||||
| # self.user = User.objects.create(username="teste", password="teste") | ||||||||||||||||||||||||||||||||||||||||||||||
| # self.collection = Collection.objects.create( | ||||||||||||||||||||||||||||||||||||||||||||||
| # creator=self.user, acron3="scl", is_active=True | ||||||||||||||||||||||||||||||||||||||||||||||
| # ) | ||||||||||||||||||||||||||||||||||||||||||||||
| # self.journal = Journal.objects.create(creator=self.user, title="Test Journal") | ||||||||||||||||||||||||||||||||||||||||||||||
| # self.scielo_journal = SciELOJournal.objects.create( | ||||||||||||||||||||||||||||||||||||||||||||||
| # issn_scielo="1516-635X", | ||||||||||||||||||||||||||||||||||||||||||||||
| # collection=self.collection, | ||||||||||||||||||||||||||||||||||||||||||||||
| # journal=self.journal, | ||||||||||||||||||||||||||||||||||||||||||||||
| # journal_acron="abdc", | ||||||||||||||||||||||||||||||||||||||||||||||
| # ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # def test_fetch_and_process_journal_logos_with_invalid_collection(self): | ||||||||||||||||||||||||||||||||||||||||||||||
| # """Test that ValueError is raised when collection does not exist""" | ||||||||||||||||||||||||||||||||||||||||||||||
| # with self.assertRaises(ValueError) as context: | ||||||||||||||||||||||||||||||||||||||||||||||
| # fetch_and_process_journal_logos_in_collection( | ||||||||||||||||||||||||||||||||||||||||||||||
| # collection_acron3="invalid_acron", | ||||||||||||||||||||||||||||||||||||||||||||||
| # username=self.user.username, | ||||||||||||||||||||||||||||||||||||||||||||||
| # ) | ||||||||||||||||||||||||||||||||||||||||||||||
| # self.assertIn("does not exist", str(context.exception)) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # @patch("journal.tasks.group") | ||||||||||||||||||||||||||||||||||||||||||||||
| # def test_fetch_and_process_journal_logos_with_valid_collection(self, mock_group): | ||||||||||||||||||||||||||||||||||||||||||||||
| # """Test that task executes with valid collection""" | ||||||||||||||||||||||||||||||||||||||||||||||
| # # Mock the group to avoid actually running the celery tasks | ||||||||||||||||||||||||||||||||||||||||||||||
| # mock_group.return_value.return_value = None | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # result = fetch_and_process_journal_logos_in_collection( | ||||||||||||||||||||||||||||||||||||||||||||||
| # collection_acron3=self.collection.acron3, | ||||||||||||||||||||||||||||||||||||||||||||||
| # username=self.user.username, | ||||||||||||||||||||||||||||||||||||||||||||||
| # ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # # The task should complete without raising an exception | ||||||||||||||||||||||||||||||||||||||||||||||
| # # and should call group with the task signatures | ||||||||||||||||||||||||||||||||||||||||||||||
| # self.assertTrue(mock_group.called) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+276
to
299
|
||||||||||||||||||||||||||||||||||||||||||||||
| # """Test that ValueError is raised when collection does not exist""" | |
| # with self.assertRaises(ValueError) as context: | |
| # fetch_and_process_journal_logos_in_collection( | |
| # collection_acron3="invalid_acron", | |
| # username=self.user.username, | |
| # ) | |
| # self.assertIn("does not exist", str(context.exception)) | |
| # @patch("journal.tasks.group") | |
| # def test_fetch_and_process_journal_logos_with_valid_collection(self, mock_group): | |
| # """Test that task executes with valid collection""" | |
| # # Mock the group to avoid actually running the celery tasks | |
| # mock_group.return_value.return_value = None | |
| # result = fetch_and_process_journal_logos_in_collection( | |
| # collection_acron3=self.collection.acron3, | |
| # username=self.user.username, | |
| # ) | |
| # # The task should complete without raising an exception | |
| # # and should call group with the task signatures | |
| # self.assertTrue(mock_group.called) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bare 'except Exception' catches all exceptions silently without logging them. If an error occurs while building the URL (e.g., Site.DoesNotExist from get_url_logo), it will be silently swallowed and the fallback will be attempted. Consider logging the exception before falling back, or catching more specific exceptions. This will help with debugging if the URL generation fails.