Skip to content

[Fixes #332] Bug: aftermath of author order id change behaivior for interacting with db in to internal values #333

Merged
mwallschlaeger merged 5 commits intomainfrom
issue_#332_Bug__aftermath_of_author_order_id_change_behaivior_for_interacting_with_db_in_`to_internal_values`
Feb 13, 2026
Merged

[Fixes #332] Bug: aftermath of author order id change behaivior for interacting with db in to internal values #333
mwallschlaeger merged 5 commits intomainfrom
issue_#332_Bug__aftermath_of_author_order_id_change_behaivior_for_interacting_with_db_in_`to_internal_values`

Conversation

@mwallschlaeger
Copy link
Member

Description

fixing requested of gemini after previous PR. Moving database interaction of contact role fields into save() method of serializer.

Type of Change

Please select the relevant option:

  • Bug fix
  • New feature
  • Documentation update
  • Refactoring
  • Other (please describe)

Related Issue

If there is an existing issue related to this pull request, please reference it here.

closes #332

Checklist

Please ensure that your pull request meets the following requirements:

  • The pull request is limited to one type (docs, feature, bug fix, etc.)
  • The pull request is as small as possible. Consider opening multiple pull requests instead of one large one.
  • The feature or bug fix has been discussed and documented in an issue beforehand.

Additional Notes

Any additional information or context regarding the pull request can be provided here.

Thank you for creating this pull request

… interacting with db in `to_internal values`"
@mwallschlaeger mwallschlaeger self-assigned this Feb 13, 2026
@mwallschlaeger mwallschlaeger added bug Something isn't working API changes to rest API labels Feb 13, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @mwallschlaeger, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a bug related to how contact role fields interact with the database, particularly concerning author order IDs. The core change involves a significant refactoring of the API serializers to shift the responsibility of persisting contact role data from the to_internal_value method to the save method. This ensures that contact role entries, including their order, are handled consistently and correctly during resource creation and updates, preventing data integrity issues. Additionally, a minor adjustment was made in the map API view to ensure subtype information is passed correctly during updates.

Highlights

  • Refactored Contact Role Persistence: Database interaction for contact role fields has been moved from the to_internal_value method of ContactRoleField to the save() method of ResourceBaseSerializer. This centralizes persistence logic and resolves issues with author order ID changes.
  • Simplified ContactRoleField: The ContactRoleField now focuses solely on extracting user and order information, delegating the actual database operations (creation, update, deletion) to the ResourceBaseSerializer's save method.
  • New Helper Methods for Contact Roles: Several new static and instance methods (_pop_contact_role_payloads, _save_contact_role_payloads, _normalize_contact_role_entries, _persist_contact_roles) have been introduced in ResourceBaseSerializer to manage the lifecycle of contact roles during resource saving.
  • Improved Order Validation: The validate_order method in ContactRoleField was renamed to validate_all_orders and its logic was simplified, with the actual ordering and assignment of default order IDs now handled during the persistence phase.
  • Map API View Update: The perform_update method in MapViewSet was adjusted to correctly pass subtype information as keyword arguments to the serializer's save() method, ensuring proper map subtype handling.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • geonode/base/api/serializers.py
    • Removed validate_user and order_users static methods from ContactRoleField.
    • Renamed validate_order to validate_all_orders in ContactRoleField and simplified its implementation.
    • Refactored to_internal_value in ContactRoleField to return processed user and order tuples, deferring database operations.
    • Added a new static method _coerce_order_value to ContactRoleField for robust conversion of order values.
    • Introduced order_users_in_contact_role static method in ResourceBaseSerializer.
    • Implemented _pop_contact_role_payloads, _save_contact_role_payloads, _normalize_contact_role_entries, and _persist_contact_roles methods in ResourceBaseSerializer to manage contact role data persistence.
    • Modified the save method in ResourceBaseSerializer to utilize the new contact role persistence logic.
  • geonode/maps/api/views.py
    • Updated the perform_update method to pass subtype as keyword arguments to serializer.save() for correct map subtype handling.
Activity
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the handling of contact roles by moving database interactions from the to_internal_value method to the save method of the serializer, which is a good architectural improvement. However, a significant logic flaw has been identified in the ContactRoleField.to_internal_value method where a loop variable is leaked, leading to incorrect data association and potential UnboundLocalError (500 Internal Server Error). Additionally, the changes introduce several other issues, including performance problems due to N+1 queries, unhandled exceptions that can cause 500 Internal Server Errors, a potential regression in validation logic, and other bugs related to improper error handling of database exceptions and null values.

mwallschlaeger and others added 3 commits February 13, 2026 12:47
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
… interacting with db in `to_internal values`"
…aivior_for_interacting_with_db_in_`to_internal_values`' of github.com:zalf-rdm/geonode into issue_#332_Bug__aftermath_of_author_order_id_change_behaivior_for_interacting_with_db_in_`to_internal_values`
@mwallschlaeger
Copy link
Member Author

/gemini review

@mwallschlaeger mwallschlaeger merged commit c620c0c into main Feb 13, 2026
2 of 3 checks passed
… interacting with db in `to_internal values`"
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the handling of contact roles by moving database interactions from the to_internal_value method to the save method of the serializer. This is a great improvement that aligns with DRF best practices, enhancing code clarity and correctness. The new implementation also introduces more efficient bulk database queries for user resolution. My review includes a suggestion to further optimize database operations for persisting contact roles using bulk methods to prevent potential N+1 query issues, and a minor cleanup suggestion to remove commented-out code.

Comment on lines +1107 to +1140
def _persist_contact_roles(instance, role_value, entries):

# No payload means wipe the entire role collection
qs = ContactRole.objects.filter(resource=instance, role=role_value)
if not entries:
qs.delete()
return

desired_contact_ids = [user.pk for user, _ in entries]
existing_contact_roles = list(qs)

# Remove contacts the client dropped before reusing remaining rows
for cr in existing_contact_roles:
if cr.contact_id not in desired_contact_ids:
cr.delete()

remaining_roles = ContactRole.objects.filter(resource=instance, role=role_value)
existing_map = {cr.contact_id: cr for cr in remaining_roles}

for user, order in entries:
cr = existing_map.get(user.pk)
if cr:
if cr.order != order:
# Update the order only when necessary to minimize writes
cr.order = order
cr.save(update_fields=["order"])
continue
existing_map[user.pk] = ContactRole.objects.create(
resource=instance,
role=role_value,
contact=user,
order=order,
)

Choose a reason for hiding this comment

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

high

The current implementation of _persist_contact_roles can be inefficient as it performs database operations (delete, save, create) inside loops. This can lead to a large number of queries (N+1 problem) when dealing with many contact roles. This can be optimized by using Django's bulk operations (bulk_delete, bulk_create, bulk_update) for significant performance improvement.

    @staticmethod
    def _persist_contact_roles(instance, role_value, entries):

        # No payload means wipe the entire role collection
        qs = ContactRole.objects.filter(resource=instance, role=role_value)
        if not entries:
            qs.delete()
            return

        # If a user is specified multiple times, the last one wins.
        unique_user_entries = {user.pk: (user, order) for user, order in entries}
        final_entries = list(unique_user_entries.values())

        desired_contact_pks = {user.pk for user, _ in final_entries}

        # Bulk delete roles that are no longer present in the payload
        qs.exclude(contact_id__in=desired_contact_pks).delete()

        # Fetch existing roles that are in the payload
        existing_map = {cr.contact_id: cr for cr in qs.filter(contact_id__in=desired_contact_pks)}

        roles_to_update = []
        roles_to_create = []

        for user, order in final_entries:
            cr = existing_map.get(user.pk)
            if cr:
                # It's an existing role, check if order needs update
                if cr.order != order:
                    cr.order = order
                    roles_to_update.append(cr)
            else:
                # It's a new role
                roles_to_create.append(
                    ContactRole(
                        resource=instance,
                        role=role_value,
                        contact=user,
                        order=order,
                    )
                )

        if roles_to_create:
            ContactRole.objects.bulk_create(roles_to_create)

        if roles_to_update:
            ContactRole.objects.bulk_update(roles_to_update, ["order"])

Comment on lines +508 to +529
# self.validate_all_orders(value)

# check input order for bad values and duplicates before doing any database updates
self.validate_order(value)
# desired_entries = []

# make sure every user entry has an order and that the order is consistent with the
# given order ids (e.g. if order 0,2,3 is given, the one without order or with invalid order will get order 4 and so on)
value = self.order_users(value)
# for user_entry in value:
# user = None
# try:
# if "username" in user_entry and "pk" in user_entry:
# user = get_user_model().objects.get(pk=user_entry["pk"])
# if user.pk != get_user_model().objects.get(username=user_entry["username"]).pk:
# raise ParseError(detail=f"username and pk do not match the same user ({user_entry}) ...", code=403)
# elif "username" in user_entry:
# user = get_user_model().objects.get(username=user_entry["username"])

desired_entries = []
new_contact_pks = []
# elif "pk" in user_entry:
# user = get_user_model().objects.get(pk=user_entry["pk"])
# except get_user_model().DoesNotExist:
# raise ParseError(detail=f"User with provided username or pk does not exist ({user_entry}) ...", code=404)
# order_value = self._coerce_order_value(user_entry.get("order"))
# desired_entries.append((user, order_value))

# val represents a single contact role entry in the input list, which can be identified by username or pk
for user_entry in value:
user = self.validate_user(user_entry)
new_contact_pks.append(user.pk)
desired_entries.append((user, user_entry["order"]))
# return desired_entries

Choose a reason for hiding this comment

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

medium

This block of commented-out code appears to be leftover from a previous implementation. It should be removed to improve code clarity and maintainability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API changes to rest API bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: aftermath of author order id change behaivior for interacting with db in to_internal values

1 participant