refactor(journal): encapsular lógica de adição de instituições no mod…#1269
refactor(journal): encapsular lógica de adição de instituições no mod…#1269robertatakenaka wants to merge 5 commits intoscieloorg:mainfrom
Conversation
…elo Journal - Adiciona métodos helper (add_publisher, add_owner, add_sponsor, add_copyright_holder) ao model Journal - Refatora am_to_core.py para utilizar os novos métodos, reduzindo a duplicação de código - Centraliza a criação de registros de histórico de instituições
There was a problem hiding this comment.
Pull request overview
This PR refactors the institution addition logic in the journal module by encapsulating repetitive code into helper methods on the Journal model. The refactoring aims to reduce code duplication and centralize the creation of institution history records.
Changes:
- Added four helper methods (
add_publisher,add_owner,add_sponsor,add_copyright_holder) to the Journal model - Refactored
am_to_core.pyto use these new methods instead of directly calling institution models - Removed unused import of institution models from
am_to_core.py
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| journal/models.py | Added four new helper methods to the Journal model for adding publisher, owner, sponsor, and copyright holder with their respective history records |
| journal/sources/am_to_core.py | Removed direct institution creation code and replaced with calls to the new Journal helper methods; removed unused imports |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ) | ||
| publisher_history = PublisherHistory.get_or_create( | ||
| institution=created_publisher, | ||
| # Usa novo método add_owner ao invés de OwnerHistory |
There was a problem hiding this comment.
The comment has trailing whitespace at the end of the line. Remove the trailing spaces for consistency with code style standards.
| # Usa novo método add_owner ao invés de OwnerHistory | |
| # Usa novo método add_owner ao invés de OwnerHistory |
| if not created_publisher and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o PublisherHistory | ||
| publisher_history = PublisherHistory.get_or_create( | ||
| institution=created_publisher, |
There was a problem hiding this comment.
The organization parameter is never used in this method. When original_data is None but organization is provided, the validation will pass but created_publisher will remain None, causing institution=None to be passed to PublisherHistory.get_or_create(). Either remove the unused organization parameter or implement the logic to use it when provided.
| if not created_owner and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o OwnerHistory | ||
| owner_history = OwnerHistory.get_or_create( | ||
| institution=created_owner, |
There was a problem hiding this comment.
The organization parameter is never used in this method. When original_data is None but organization is provided, the validation will pass but created_owner will remain None, causing institution=None to be passed to OwnerHistory.get_or_create(). Either remove the unused organization parameter or implement the logic to use it when provided.
| if not created_sponsor and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o SponsorHistory | ||
| sponsor_history = SponsorHistory.get_or_create( | ||
| institution=created_sponsor, |
There was a problem hiding this comment.
The organization parameter is never used in this method. When original_data is None but organization is provided, the validation will pass but created_sponsor will remain None, causing institution=None to be passed to SponsorHistory.get_or_create(). Either remove the unused organization parameter or implement the logic to use it when provided.
journal/models.py
Outdated
| if not created_copyright_holder and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
There was a problem hiding this comment.
The organization parameter is never used in this method. When original_data is None but organization is provided, the validation will pass but created_copyright_holder will remain None, causing institution=None to be passed to CopyrightHolderHistory.get_or_create(). Either remove the unused organization parameter or implement the logic to use it when provided.
| if not created_copyright_holder and not organization: | |
| raise ValueError("Either original_data or organization must be provided") | |
| # Se nenhum CopyrightHolder foi criado/encontrado, não é seguro continuar. | |
| if not created_copyright_holder: | |
| if organization is not None: | |
| # Ainda não há lógica para derivar um CopyrightHolder a partir de organization. | |
| # Evita criar um histórico com institution=None. | |
| raise ValueError( | |
| "original_data must be provided to create a CopyrightHolder " | |
| "when organization is used." | |
| ) | |
| raise ValueError("Either original_data or organization must be provided") |
journal/models.py
Outdated
| level_2=None, | ||
| level_3=None, | ||
| user=user, | ||
| location=None, # Pode ser ajustado conforme necessário |
There was a problem hiding this comment.
The refactoring changed the behavior: the old code passed the location variable (created on line 199) to Publisher.get_or_create(), but the new add_publisher() method hardcodes location=None. This may result in data loss if location information was previously being stored. Consider adding a location parameter to add_publisher() and passing it through, or verify this change is intentional.
journal/models.py
Outdated
| level_2=None, | ||
| level_3=None, | ||
| user=user, | ||
| location=None, # Pode ser ajustado conforme necessário |
There was a problem hiding this comment.
The refactoring changed the behavior: the old code passed the location variable to Owner.get_or_create(), but the new add_owner() method hardcodes location=None. This may result in data loss if location information was previously being stored. Consider adding a location parameter to add_owner() and passing it through, or verify this change is intentional.
- Atualiza métodos add_publisher, add_owner, add_sponsor e add_copyright_holder para aceitarem o parâmetro location. - Atualiza funções auxiliares em am_to_core.py para propagar o objeto location. - Adiciona a função centralizada create_location_and_add_institutions para facilitar a criação de registros vinculados.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """ | ||
| Ex copyright_holder_name: | ||
| [{'_': 'Departamento de História da Universidade Federal Fluminense - UFF'}] | ||
|
|
There was a problem hiding this comment.
There is a trailing space at the end of this docstring line. Remove the trailing whitespace.
| if not created_publisher and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o PublisherHistory | ||
| publisher_history = PublisherHistory.get_or_create( | ||
| institution=created_publisher, | ||
| initial_date=initial_date, | ||
| final_date=final_date, | ||
| user=user, | ||
| ) | ||
| publisher_history.journal = self | ||
| publisher_history.save() | ||
| return publisher_history |
There was a problem hiding this comment.
The organization parameter is accepted but never used in the method. When original_data is None and organization is provided, the method raises a ValueError even though it should use the organization to create the history. The same issue exists in all four methods (add_publisher, add_owner, add_sponsor, add_copyright_holder). Either remove the unused organization parameter from all methods, or implement the logic to handle it by passing it to PublisherHistory.get_or_create(). Note that the History models support both institution and organization fields.
| if not created_owner and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o OwnerHistory | ||
| owner_history = OwnerHistory.get_or_create( | ||
| institution=created_owner, | ||
| initial_date=initial_date, | ||
| final_date=final_date, | ||
| user=user, | ||
| ) | ||
| owner_history.journal = self | ||
| owner_history.save() | ||
| return owner_history |
There was a problem hiding this comment.
The organization parameter is accepted but never used in the method. When original_data is None and organization is provided, the method raises a ValueError even though it should use the organization to create the history. Either remove the unused organization parameter, or implement the logic to handle it by passing it to OwnerHistory.get_or_create().
| if not created_sponsor and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o SponsorHistory | ||
| sponsor_history = SponsorHistory.get_or_create( | ||
| institution=created_sponsor, | ||
| initial_date=initial_date, | ||
| final_date=final_date, | ||
| user=user, | ||
| ) | ||
| sponsor_history.journal = self | ||
| sponsor_history.save() | ||
| return sponsor_history |
There was a problem hiding this comment.
The organization parameter is accepted but never used in the method. When original_data is None and organization is provided, the method raises a ValueError even though it should use the organization to create the history. Either remove the unused organization parameter, or implement the logic to handle it by passing it to SponsorHistory.get_or_create().
| level_1=None, | ||
| level_2=None, | ||
| level_3=None, | ||
| # Usa novo método add_owner ao invés de OwnerHistory |
There was a problem hiding this comment.
There is a trailing space at the end of this comment line. Remove the trailing whitespace.
| # Usa novo método add_owner ao invés de OwnerHistory | |
| # Usa novo método add_owner ao invés de OwnerHistory |
| def create_location_and_add_institutions( | ||
| journal, | ||
| publisher=None, | ||
| copyright_holder=None, | ||
| sponsor=None, | ||
| address=None, | ||
| publisher_country=None, | ||
| publisher_state=None, | ||
| publisher_city=None, | ||
| user=None, | ||
| ): | ||
| """ | ||
| Função centralizada para criar location e adicionar todas as instituições ao journal. | ||
|
|
||
| Args: | ||
| journal: O objeto Journal | ||
| publisher: Dados do publisher (string ou lista) | ||
| copyright_holder: Dados do copyright holder | ||
| sponsor: Dados do sponsor (string ou lista) | ||
| address: Endereço para criação do location | ||
| publisher_country: País do publisher | ||
| publisher_state: Estado do publisher | ||
| publisher_city: Cidade do publisher | ||
| user: Usuário responsável pela criação | ||
|
|
||
| Returns: | ||
| location: Objeto Location criado/atualizado | ||
| """ | ||
| if not user: | ||
| raise ValueError("User parameter is required") | ||
|
|
||
| # Cria ou atualiza o location | ||
| location = create_or_update_location( | ||
| journal=journal, | ||
| address=address, | ||
| publisher_country=publisher_country, | ||
| publisher_state=publisher_state, | ||
| publisher_city=publisher_city, | ||
| user=user, | ||
| ) | ||
|
|
||
| # Adiciona publisher(s) e owner(s) se fornecido | ||
| if publisher: | ||
| publisher_data = extract_value(publisher) | ||
| if isinstance(publisher_data, str): | ||
| publisher_data = re.split(r"\s*[-\/,]\s*", publisher_data) | ||
|
|
||
| if publisher_data: | ||
| for p in publisher_data: | ||
| if p: | ||
| # Adiciona publisher | ||
| journal.add_publisher( | ||
| user=user, | ||
| original_data=p, | ||
| location=location, | ||
| ) | ||
| # Adiciona owner (mesmo dado do publisher por padrão) | ||
| journal.add_owner( | ||
| user=user, | ||
| original_data=p, | ||
| location=location, | ||
| ) | ||
|
|
||
| # Adiciona sponsor(s) se fornecido | ||
| if sponsor: | ||
| get_or_create_sponsor( | ||
| journal=journal, | ||
| sponsor=sponsor, | ||
| user=user, | ||
| location=location | ||
| ) | ||
|
|
||
| # Adiciona copyright holder(s) se fornecido | ||
| if copyright_holder: | ||
| get_or_create_copyright_holder( | ||
| journal=journal, | ||
| copyright_holder_name=copyright_holder, | ||
| user=user, | ||
| location=location | ||
| ) | ||
|
|
||
| return location |
There was a problem hiding this comment.
The new function create_location_and_add_institutions is defined but never called anywhere in the codebase. This appears to be dead code that was added but not integrated. Either remove this function if it's not needed, or integrate it into the codebase by replacing the existing logic in update_panel_institution to use this centralized function.
journal/models.py
Outdated
| def add_publisher(self, user, organization=None, original_data=None, | ||
| initial_date=None, final_date=None, location=None): | ||
| """Adiciona publisher usando PublisherHistory.""" | ||
| created_publisher = None | ||
| if original_data: | ||
| # Cria/busca o Publisher baseado nos dados originais | ||
| created_publisher = Publisher.get_or_create( | ||
| name=original_data, | ||
| acronym=None, | ||
| level_1=None, | ||
| level_2=None, | ||
| level_3=None, | ||
| user=user, | ||
| location=location, | ||
| official=None, | ||
| is_official=None, | ||
| url=None, | ||
| institution_type=None, | ||
| ) | ||
| if not created_publisher and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o PublisherHistory | ||
| publisher_history = PublisherHistory.get_or_create( | ||
| institution=created_publisher, | ||
| initial_date=initial_date, | ||
| final_date=final_date, | ||
| user=user, | ||
| ) | ||
| publisher_history.journal = self | ||
| publisher_history.save() | ||
| return publisher_history | ||
|
|
||
| def add_owner(self, user, organization=None, original_data=None, | ||
| initial_date=None, final_date=None, location=None): | ||
| """Adiciona owner usando OwnerHistory.""" | ||
| created_owner = None | ||
| if original_data: | ||
| # Cria/busca o Owner baseado nos dados originais | ||
| created_owner = Owner.get_or_create( | ||
| name=original_data, | ||
| acronym=None, | ||
| level_1=None, | ||
| level_2=None, | ||
| level_3=None, | ||
| user=user, | ||
| location=location, | ||
| official=None, | ||
| is_official=None, | ||
| url=None, | ||
| institution_type=None, | ||
| ) | ||
| if not created_owner and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o OwnerHistory | ||
| owner_history = OwnerHistory.get_or_create( | ||
| institution=created_owner, | ||
| initial_date=initial_date, | ||
| final_date=final_date, | ||
| user=user, | ||
| ) | ||
| owner_history.journal = self | ||
| owner_history.save() | ||
| return owner_history | ||
|
|
||
| def add_sponsor(self, user, organization=None, original_data=None, | ||
| initial_date=None, final_date=None, location=None): | ||
| """Adiciona sponsor usando SponsorHistory.""" | ||
| created_sponsor = None | ||
| if original_data: | ||
| # Cria/busca o Sponsor baseado nos dados originais | ||
| created_sponsor = Sponsor.get_or_create( | ||
| name=original_data, | ||
| acronym=None, | ||
| level_1=None, | ||
| level_2=None, | ||
| level_3=None, | ||
| user=user, | ||
| location=location, | ||
| official=None, | ||
| is_official=None, | ||
| url=None, | ||
| institution_type=None, | ||
| ) | ||
| if not created_sponsor and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o SponsorHistory | ||
| sponsor_history = SponsorHistory.get_or_create( | ||
| institution=created_sponsor, | ||
| initial_date=initial_date, | ||
| final_date=final_date, | ||
| user=user, | ||
| ) | ||
| sponsor_history.journal = self | ||
| sponsor_history.save() | ||
| return sponsor_history | ||
|
|
||
| def add_copyright_holder(self, user, organization=None, original_data=None, | ||
| initial_date=None, final_date=None, location=None): | ||
| """Adiciona copyright_holder usando CopyrightHolderHistory.""" | ||
| created_copyright_holder = None | ||
| if original_data: | ||
| # Cria/busca o CopyrightHolder baseado nos dados originais | ||
| created_copyright_holder = CopyrightHolder.get_or_create( | ||
| name=original_data, | ||
| acronym=None, | ||
| level_1=None, | ||
| level_2=None, | ||
| level_3=None, | ||
| user=user, | ||
| location=location, | ||
| official=None, | ||
| is_official=None, | ||
| url=None, | ||
| institution_type=None, | ||
| ) | ||
| if not created_copyright_holder and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o CopyrightHolderHistory | ||
| copyright_holder_history = CopyrightHolderHistory.get_or_create( | ||
| institution=created_copyright_holder, | ||
| initial_date=initial_date, | ||
| final_date=final_date, | ||
| user=user, | ||
| ) | ||
| copyright_holder_history.journal = self | ||
| copyright_holder_history.save() | ||
| return copyright_holder_history |
There was a problem hiding this comment.
The new methods add_publisher, add_owner, add_sponsor, and add_copyright_holder lack test coverage. Since the codebase includes comprehensive automated testing for other journal functionality (as seen in journal/tests.py), these new methods should have corresponding unit tests to verify their behavior, including edge cases like when neither original_data nor organization is provided, when both are provided, and when the History records already exist.
| if not created_copyright_holder and not organization: | ||
| raise ValueError("Either original_data or organization must be provided") | ||
|
|
||
| # Cria/busca o CopyrightHolderHistory | ||
| copyright_holder_history = CopyrightHolderHistory.get_or_create( | ||
| institution=created_copyright_holder, | ||
| initial_date=initial_date, | ||
| final_date=final_date, | ||
| user=user, | ||
| ) |
There was a problem hiding this comment.
The organization parameter is accepted but never used in the method. When original_data is None and organization is provided, the method raises a ValueError even though it should use the organization to create the history. Either remove the unused organization parameter, or implement the logic to handle it by passing it to CopyrightHolderHistory.get_or_create().
| if not created_copyright_holder and not organization: | |
| raise ValueError("Either original_data or organization must be provided") | |
| # Cria/busca o CopyrightHolderHistory | |
| copyright_holder_history = CopyrightHolderHistory.get_or_create( | |
| institution=created_copyright_holder, | |
| initial_date=initial_date, | |
| final_date=final_date, | |
| user=user, | |
| ) | |
| # É necessário fornecer pelo menos original_data ou organization | |
| if not original_data and not organization: | |
| raise ValueError("Either original_data or organization must be provided") | |
| # Cria/busca o CopyrightHolderHistory | |
| if organization is not None: | |
| copyright_holder_history = CopyrightHolderHistory.get_or_create( | |
| organization=organization, | |
| initial_date=initial_date, | |
| final_date=final_date, | |
| user=user, | |
| ) | |
| else: | |
| copyright_holder_history = CopyrightHolderHistory.get_or_create( | |
| institution=created_copyright_holder, | |
| initial_date=initial_date, | |
| final_date=final_date, | |
| user=user, | |
| ) |
| copyright_holder: Dados do copyright holder | ||
| sponsor: Dados do sponsor (string ou lista) | ||
| address: Endereço para criação do location | ||
| publisher_country: País do publisher | ||
| publisher_state: Estado do publisher | ||
| publisher_city: Cidade do publisher | ||
| user: Usuário responsável pela criação | ||
|
|
||
| Returns: | ||
| location: Objeto Location criado/atualizado |
There was a problem hiding this comment.
There are trailing spaces at the end of these comment lines. Remove the trailing whitespace from lines 892, 896, and 901.
- Atualiza add_publisher, add_owner, add_sponsor e add_copyright_holder para passarem o parâmetro 'organization' ao criar históricos. - Padroniza o uso de aspas duplas em consultas select_related e values_list. - Ajusta identação de métodos e strings de ajuda (help_text) para conformidade com o linter.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 17 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| organization = Organization.objects.create( | ||
| name=manual_name, | ||
| location=location, | ||
| source='user', # Marca como fonte user | ||
| ) | ||
| return organization | ||
| except Exception: | ||
| # Se não conseguir criar, retorna None - usuário terá que usar lista padrão | ||
| return None |
There was a problem hiding this comment.
Silently catching all exceptions with bare 'except Exception' and returning None can hide legitimate errors during organization creation. This makes debugging difficult and could lead to data loss. Consider logging the exception or allowing critical errors to propagate while only catching specific expected exceptions.
| try: | ||
| # Busca ou cria location (simplificado) | ||
| location_data = {'country': manual_country} | ||
| location, created = Location.objects.get_or_create(**location_data) | ||
| except Exception: | ||
| location = None |
There was a problem hiding this comment.
Silently catching all exceptions with bare 'except Exception' and returning None can hide legitimate errors during location creation. This makes debugging difficult. Consider logging the exception or allowing critical errors to propagate while only catching specific expected exceptions.
| level_1=None, | ||
| level_2=None, | ||
| level_3=None, | ||
| # Usa novo método add_owner ao invés de OwnerHistory |
There was a problem hiding this comment.
There's trailing whitespace at the end of this comment line.
| # Usa novo método add_owner ao invés de OwnerHistory | |
| # Usa novo método add_owner ao invés de OwnerHistory |
| owner_history = OwnerHistory.get_or_create( | ||
| institution=created_owner, | ||
| organization=organization, | ||
| initial_date=initial_date, | ||
| final_date=final_date, | ||
| user=user, | ||
| ) |
There was a problem hiding this comment.
The get_or_create method being called here is inherited from BaseHistoryItem, which only accepts parameters (institution, initial_date, final_date, user). The organization parameter being passed here will be ignored by get_or_create, meaning the organization field will never be set on the created/retrieved history object. This will result in history records without organization data even when organization is provided. The method should either override get_or_create to handle organization, or manually set the organization field after calling get_or_create.
| manual_city = self.cleaned_data.get('manual_city') | ||
| manual_state = self.cleaned_data.get('manual_state') |
There was a problem hiding this comment.
The variables manual_city and manual_state are retrieved from cleaned_data but are never used in the organization creation logic. Either these should be used when creating the organization (perhaps stored in a related field or custom attributes), or they should be removed if not needed. This suggests incomplete implementation or unnecessary code.
| manual_city = self.cleaned_data.get('manual_city') | |
| manual_state = self.cleaned_data.get('manual_state') |
| manual_city = self.cleaned_data.get('manual_city') | ||
| manual_state = self.cleaned_data.get('manual_state') |
There was a problem hiding this comment.
Variable manual_city is not used.
| manual_city = self.cleaned_data.get('manual_city') | |
| manual_state = self.cleaned_data.get('manual_state') |
| """Cria ou busca Organization baseado nos dados manuais.""" | ||
| manual_name = self.cleaned_data.get('manual_org_name') | ||
| manual_city = self.cleaned_data.get('manual_city') | ||
| manual_state = self.cleaned_data.get('manual_state') |
There was a problem hiding this comment.
Variable manual_state is not used.
| manual_state = self.cleaned_data.get('manual_state') |
| class SponsorHistoryForm(OrganizationMixin, CoreAdminModelForm): | ||
| def __init__(self, *args, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
| # Define o model dinamicamente para evitar importação circular | ||
| if not hasattr(self._meta, 'model') or self._meta.model is None: | ||
| from journal.models import SponsorHistory | ||
| self._meta.model = SponsorHistory | ||
|
|
||
|
|
There was a problem hiding this comment.
This assignment to 'SponsorHistoryForm' is unnecessary as it is redefined before this value is used.
| class SponsorHistoryForm(OrganizationMixin, CoreAdminModelForm): | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| # Define o model dinamicamente para evitar importação circular | |
| if not hasattr(self._meta, 'model') or self._meta.model is None: | |
| from journal.models import SponsorHistory | |
| self._meta.model = SponsorHistory |
|
outros 2 pr o substituirão |
O que esse PR faz?
Este PR realiza uma refatoração no modelo
Journalpara encapsular a lógica de criação e associação de instituições vinculadas ao histórico do periódico (Publishers, Owners, Sponsors e Copyright Holders).As principais mudanças incluem:
add_publisher,add_owner,add_sponsoreadd_copyright_holderdiretamente na classeJournal.am_to_core.py, substituindo blocos repetitivos de código por chamadas simplificadas aos novos métodos do modelo.Onde a revisão poderia começar?
A revisão deve começar pelo arquivo
journal/models.py, onde os novos métodos foram implementados para entender a lógica de abstração. Em seguida, verifiquejournal/sources/am_to_core.pypara validar como o código ficou mais enxuto após a substituição das chamadas diretas aos modelos de instituição.Como este poderia ser testado manualmente?
am_to_core.py).PublisherHistory,SponsorHistory, etc.) foram gerados e vinculados aoJournale aoUsercorretos.journal_instance.add_publisher(user=user, original_data="Nome da Editora").Algum cenário de contexto que queira dar?
Anteriormente, a lógica de instanciar uma instituição e logo em seguida criar seu registro de histórico estava espalhada por diversos pontos dos scripts de conversão. Isso gerava código duplicado e dificultava a manutenção caso o esquema do histórico mudasse. Ao mover essa lógica para o
Journal, tornamos o código mais "Django-like" e facilitamos a reutilização em outras partes do sistema.Screenshots
N/A (Alteração de backend/lógica de modelos)
Quais são tickets relevantes?
Referências