Skip to content
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

Fix issue #261: Handle unique constraint validation error #777

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

AkhileshMuthusamy
Copy link

Issue: When a translation field is added to the unique constraint, the validation error is not captured by the TranslationAdmin causing 500 error.

Root cause: The TranslationAdmin removes the original field from the form and the BaseModelForm does not perform validation check for the fields that are not present in the form, resulting in unhandled exception when Django attempts to create or update the instance.

Fix: The original field is added back to the form fields after the form is rendered, allowing BaseModelForm to perform unique constraint validation for translation fields.

@last-partizan
Copy link
Collaborator

That's interesting issue.

Can you please show how it's looking now on the screenshots?

@AkhileshMuthusamy
Copy link
Author

I have created a Django project to reproduce the issue. You can clone the repo to verify the fix. I have also attached the video for reference on how the error would look like.

git clone https://github.com/AkhileshMuthusamy/sample_django_project_for_testing.git

Use the master branch to reproduce the issue.

Checkout the bug/handle-unique-constraint branch to test the fix.

model_translation_unique_contraint_fix.webm

@last-partizan
Copy link
Collaborator

Thanks for detailed reproduction.

I have updates our tests, and now you can modify test_create_duplicate to test if this fix is working in CI.

Sync your branch with master, and try running tests locally.

@AkhileshMuthusamy
Copy link
Author

I have synced the branch with master and updated the test test_create_duplicate. It's ready for review.

Thanks for adding tests.

Comment on lines +256 to +259
if kwargs.get("fields"):
original_fields = [field for field in self.trans_opts.fields if field not in exclude]
kwargs["fields"].extend(original_fields)
kwargs.update({"exclude": (updated_exclude and list({*exclude, *updated_exclude}))})
Copy link
Collaborator

Choose a reason for hiding this comment

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

I cleaned it up a bit, and fixed type errors.

And now we can discuss what's happening here.

This branch is where new code added, and it looks like we're adding "original fields" again ... after they were removed? Maybe it's better to look for a place where they were removed?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, what surprised me here is that we have added back original fields, but none of the tests failed.

I have added test_model_with_constraint_fields in master, and synced it here. But it still detects only title, sub_title_de, sub_title_en fields. This is good, because form looks like before, and bad, because I do not understand why :)

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for cleaning the code.
While debugging I noticed the _get_fieldsets_post_form_or_formset is where the translation fields get added (I could be wrong). Does it make sense to move the logic to add original fields here?

I am not entirely sure how this change fixed the issue. I made the changes purely based on my observation on how the code is executed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, I see there's replace_orig_field, and it looks like it replaces original fields with translation ones.

We can add option preserve_original_fields with default value False here. And now, instead of adding fields back - we just preserve it here.

I'm not sure if we need preserve_original_fields=False at all, maybe just make preserving originals default behaviour. Check how it works.

Copy link
Author

Choose a reason for hiding this comment

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

Good idea. I will test it.

Comment on lines +260 to +262
else:
updated_exclude = self._exclude_original_fields(updated_exclude)
kwargs.update({"exclude": updated_exclude})
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is basically the same as removed 243-244 lines. Right?

But, something feels not right here. If we have "fields" specified we're making changes to a code path, but when no fields specified - we're doing all like before?

(I may be wrong here, and maybe we're always have "fields" specified? But looking at the code coverage, this branch is also covered/executed, so it worth investigating)

Copy link
Author

Choose a reason for hiding this comment

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

While debugging I noticed the _get_form_or_formset is called twice, one with fields and one without fields (attached screenshot). If I remove the else block then the translation fields are displayed twice. So, I had to retain the existing logic in the else block.

mt_terminal_log

@last-partizan
Copy link
Collaborator

Also. maybe we don't need all this and can use another way.

Validation errors for unique fields is collected at django BaseModelForm.validate_unique and we only need to remove some exclusions from _get_validation_exclusions. In theory. And maybe this will be simpler.

I'll be grateful if you can check this idea.

@AkhileshMuthusamy
Copy link
Author

Sure. I will look into that.

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.

2 participants