Skip to content

Conversation

@robertatakenaka
Copy link
Member

O que esse PR faz?

Fale sobre o propósito do pull request como por exemplo: quais problemas ele soluciona ou quais features ele adiciona.

Onde a revisão poderia começar?

Indique o caminho do arquivo e o arquivo onde o revisor deve iniciar a leitura do código.

Como este poderia ser testado manualmente?

Estabeleça os passos necessários para que a funcionalidade seja testada manualmente pelo revisor.

Algum cenário de contexto que queira dar?

Indique um contexto onde as modificações se fazem necessárias ou passe informações que contextualizam
o revisor a fim de facilitar o entendimento da funcionalidade.

Screenshots

Quando aplicável e se fizer possível adicione screenshots que remetem a situação gráfica do problema que o pull request resolve.

Quais são tickets relevantes?

Indique uma issue ao qual o pull request faz relacionamento.

Referências

Indique as referências utilizadas para a elaboração do pull request.

Copilot AI review requested due to automatic review settings January 30, 2026 22:52
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the organization/institution models from version 2 to version 3, introducing new models and restructuring the existing ones. The title indicates this is work in progress ("wip").

Changes:

  • Introduces new models: OrganizationalLevel, RawOrganization, and BaseOrganizationalLevel
  • Refactors Organization model to remove institution_type_mec, institution_type_scielo, and is_official fields
  • Adds new fields: source, external_id to Organization and OrganizationInstitutionType
  • Adds new utility function clean_xml_tag_content to standardize text input
  • Updates institution models with documentation about their legacy status (version 1)

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 21 comments.

Show a summary per file
File Description
organization/models.py Major refactoring of Organization models, adding RawOrganization and OrganizationalLevel, removing old fields and methods
organization/migrations/0011_organizationallevel_raworganization_and_more.py Django migration creating new models and altering existing ones
organization/choices.py Replaces inst_type tuple with SOURCE_CHOICES list
institution/models.py Adds documentation marking these models as version 1 (legacy)
core/utils/standardizer.py Adds clean_xml_tag_content and remove_html_tags functions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

field=models.ManyToManyField(
blank=True,
help_text="Standardized levels (requires organization)",
related_name="raw_organization_leves",
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in related_name: "raw_organization_leves" should be "raw_organization_levels" (missing 'l'). This should match the corrected name in the model.

Suggested change
related_name="raw_organization_leves",
related_name="raw_organization_levels",

Copilot uses AI. Check for mistakes.
class Meta:
verbose_name = _("Organization")
verbose_name_plural = _("Organizations")
unique_together = [("name", "location", "external_id", "source")] # NOVO
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unique_together constraint includes 'external_id' and 'source' which are nullable fields. This means multiple records with NULL values for these fields can exist, which may not provide the intended uniqueness. If the intent is to allow multiple organizations with the same name in the same location but different sources/external_ids, consider adding a database constraint that handles NULLs appropriately, or ensure that source always has a value (remove null=True).

Copilot uses AI. Check for mistakes.
Comment on lines +844 to +906
def get(cls, name, original=None, normalized_name=None, acronym=None, country=None, state=None, city=None, level_1=None, level_2=None, level_3=None):
"""
Busca registro existente.
"""
if not name:
raise ValueError("RawOrganization.get requires name")

original = clean_xml_tag_content(original)
normalized_name = clean_xml_tag_content(normalized_name)
name = clean_xml_tag_content(name)
country = clean_xml_tag_content(country)
state = clean_xml_tag_content(state)
city = clean_xml_tag_content(city)
level_1 = clean_xml_tag_content(level_1)
level_2 = clean_xml_tag_content(level_2)
level_3 = clean_xml_tag_content(level_3)

params = {"name__iexact": name}

if original:
params["original__iexact"] = original
else:
params["original__isnull"] = True

if normalized_name:
params["normalized_name__iexact"] = normalized_name
else:
params["normalized_name__isnull"] = True

if country:
params["country__iexact"] = country
else:
params["country__isnull"] = True

if state:
params["state__iexact"] = state
else:
params["state__isnull"] = True

if city:
params["city__iexact"] = city
else:
params["city__isnull"] = True

if level_1:
params["level_1__iexact"] = level_1
else:
params["level_1__isnull"] = True

if level_2:
params["level_2__iexact"] = level_2
else:
params["level_2__isnull"] = True

if level_3:
params["level_3__iexact"] = level_3
else:
params["level_3__isnull"] = True

try:
return cls.objects.get(**params)
except cls.MultipleObjectsReturned:
return cls.objects.filter(**params).first()
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get method builds query parameters checking for None values and setting __isnull conditions. However, the method doesn't handle the case where 'acronym' is provided but is None in the parameters - line 844 shows acronym is accepted but never checked in the params dictionary construction. This inconsistency could lead to unexpected query behavior. Consider adding similar handling for acronym as done for other optional fields.

Copilot uses AI. Check for mistakes.
Comment on lines +1026 to +1029
if organization is not None:
obj.organization = organization
if organizational_level is not None:
obj.organizational_level = organizational_level
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot assign ManyToMany fields directly. Lines 1027, 1029 attempt to assign values directly to ManyToMany fields 'organization' and 'organizational_level'. This will raise a TypeError. Use .set() or .add() methods instead, and these operations can only be performed after the object is saved.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +40
if text_.isalpha():
return text_
else:
return None
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in clean_xml_tag_content is problematic. When assert_string=True (which is the default), it uses text_.isalpha() to validate the string. This method returns True only if ALL characters are alphabetic (no spaces, numbers, or special characters). This means that most organization names like "Universidade de São Paulo", "USP-2024", or any name with spaces will be rejected and return None. This will cause data loss when cleaning organization names that contain spaces, numbers, or special characters.

Suggested change
if text_.isalpha():
return text_
else:
return None
# Accept strings that contain at least one alphabetic character.
# This allows spaces, digits, and punctuation in names (e.g., "USP-2024"),
# while still rejecting strings with no letters at all.
if any(ch.isalpha() for ch in text_):
return text_
return None

Copilot uses AI. Check for mistakes.
organizational_level = models.ManyToManyField(
"OrganizationalLevel",
blank=True,
related_name="raw_organization_leves",
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in related_name: "raw_organization_leves" should be "raw_organization_levels" (missing 'l').

Suggested change
related_name="raw_organization_leves",
related_name="raw_organization_levels",

Copilot uses AI. Check for mistakes.
@property
def is_matched(self):
"""Indica se está vinculado a Organization."""
return self.organization_id is not None
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The is_matched property checks 'self.organization_id' but 'organization' is defined as a ManyToManyField, not a ForeignKey. ManyToMany fields don't have an '_id' attribute. This will raise an AttributeError. To check if a ManyToMany relationship exists, use 'self.organization.exists()' instead (though this requires the object to be saved first).

Suggested change
return self.organization_id is not None
return self.organization.exists()

Copilot uses AI. Check for mistakes.
organization=organization,
organizational_level=organizational_level,
match_status=match_status or "unmatched",
extra_data=extra_data,
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The create method references 'extra_data' field (line 949) which is not defined in the RawOrganization model. This will raise a TypeError when trying to create an instance with this parameter.

Suggested change
extra_data=extra_data,

Copilot uses AI. Check for mistakes.
Comment on lines +1072 to +1073
self.organization = organization
self.organizational_level = organizational_level
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot assign ManyToMany fields directly. Lines 1072-1073 attempt to assign values directly to ManyToMany fields 'organization' and 'organizational_level'. This will raise a TypeError. These should use .set() or .add() methods: e.g., 'self.organization.set([organization])' if organization is a single instance, or 'self.organization.add(organization)'.

Copilot uses AI. Check for mistakes.
from core.forms import CoreAdminModelForm
from core.models import BaseHistory, CommonControlField
from core.utils.standardizer import remove_extra_spaces
from core.utils.standardizer import clean_xml_tag_content, remove_extra_spaces
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'remove_extra_spaces' is not used.

Suggested change
from core.utils.standardizer import clean_xml_tag_content, remove_extra_spaces
from core.utils.standardizer import clean_xml_tag_content

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant