Skip to content

Add non-empty constraint for UserSocialAuth uid #557

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

aleksanb
Copy link

Proposed changes

Any empty uids delivered by providers are surely erroneous, and better to have integrity errors than having users start sharing accounts when logging in.

A DB check constraint will catch issues like this one, and ensure that no matter the way a usersocialauth model is created, no empty uids can be inserted.

I'd say this is a bugfix mostly, but a breaking bugfix at that, as existing users of this library might have uid='' in their database by accident. These are real errors that those users will want to fix, and we should mention this upgrade in the changelog as a breaking change.

Types of changes

Please check the type of change your PR introduces:

  • Release (new release request)
  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Code style update (PEP8, lint, formatting, renaming, etc)
  • Refactoring (no functional changes, no api changes)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Build related changes (build process, tests runner, etc)
  • Other (please describe):

Checklist

Put an x in the boxes that apply. You can also fill these out after creating
the PR. If you're unsure about any of them, don't hesitate to ask. We're here to
help! This is simply a reminder of what we are going to look for before merging
your code.

@aleksanb aleksanb force-pushed the check-for-empty-string branch from 5f306dd to 2a4d46a Compare March 22, 2024 11:50
@aleksanb
Copy link
Author

aleksanb commented Apr 3, 2024

Any thoughts on whether this could be accepted?

@nijel
Copy link
Member

nijel commented Apr 3, 2024

I think it makes sense, but I'm not sure about splitting the constraint to UserSocialAuth instead of AbstractUserSocialAuth. Also this makes the tests fail.

@aleksanb
Copy link
Author

aleksanb commented Apr 3, 2024

I can move the constraint up to AbstractUserSocialAuth, i'm not familiar with the difference between AbstractUserSocialAuth and UserSocialAuth though.

I'll have a look at the test failure.

@nijel
Copy link
Member

nijel commented Apr 3, 2024

I'm not familiar with it either, I'm just curious about splitting the constraint from the field definition.

@stianjensen
Copy link

It would probably work as long as the Meta-class of UserSocialAuth is also updated to explicitly subclass AbstractUserSocialAuth.Meta, like this: class Meta(AbstractUserSocialAuth.Meta): (and as long as UserSocialAuth does not get its own definition of constraints in the future that overrides this one – although if that happened, it would be detected by seeing a new migration operation appearing).

@nijel
Copy link
Member

nijel commented Mar 14, 2025

@stianjensen Can you please rebase this? As there was no other feedback, I think it's okay to merge as is, I just want to see the tests pass.

@aleksanb aleksanb force-pushed the check-for-empty-string branch 7 times, most recently from a17f0d6 to 014f43e Compare March 14, 2025 12:16
@aleksanb
Copy link
Author

Pushed up a rebased and updated version @nijel , but i can't figure out how to run the tests locally, can you approve CI for tests?

migrations.AddConstraint(
model_name="usersocialauth",
constraint=models.CheckConstraint(
condition=models.Q(("uid", ""), _negated=True),
Copy link
Member

Choose a reason for hiding this comment

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

Fails with TypeError: __init__() got an unexpected keyword argument 'condition'.

Perhaps need a more recent Django version and the dependencies need to be adjusted?

Copy link
Author

Choose a reason for hiding this comment

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

yeah, i'll need to use check to support 3.2 i see.

Copy link
Author

Choose a reason for hiding this comment

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

hm, i have check in the models.py but django generated condition because i used a newer django generating the migration. I'll modify it by hand to use check

Copy link
Author

Choose a reason for hiding this comment

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

updated

@aleksanb aleksanb force-pushed the check-for-empty-string branch from 014f43e to cf38c01 Compare March 14, 2025 13:12
@nijel
Copy link
Member

nijel commented Mar 14, 2025

There is also pre-commit fixing some formatting. Can you allow edits to the PR so that it can push the fixes?

@nijel
Copy link
Member

nijel commented Mar 14, 2025

...and the constraint fails some checks:

sqlite3.IntegrityError: CHECK constraint failed: user_social_auth_uid_required

@aleksanb aleksanb force-pushed the check-for-empty-string branch from cf38c01 to 1e1c733 Compare March 14, 2025 16:08
@aleksanb
Copy link
Author

Seems like i can't enable maintainer edits because my fork lives in an organization.

Locally, ruff seems to pass but the local commands might not be the same that CI runs.
(.venv) ➜ social-app-django git:(check-for-empty-string) pre-commit run ruff --all-files
ruff.....................................................................Passed

I added an uid to the one test that failed on CI.

Copy link

codecov bot commented Mar 14, 2025

Codecov Report

Attention: Patch coverage is 81.81818% with 2 lines in your changes missing coverage. Please review.

Project coverage is 91.63%. Comparing base (fb93814) to head (d0182d1).
Report is 118 commits behind head on master.

Files with missing lines Patch % Lines
social_django/models.py 60.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #557      +/-   ##
==========================================
- Coverage   91.72%   91.63%   -0.10%     
==========================================
  Files          39       41       +2     
  Lines        1172     1195      +23     
  Branches      144       67      -77     
==========================================
+ Hits         1075     1095      +20     
- Misses         72       74       +2     
- Partials       25       26       +1     
Flag Coverage Δ
unittests 91.63% <81.81%> (-0.10%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@aleksanb
Copy link
Author

Django main fails now because check has been renamed to condition, but that change doesn't happen until django 6.0.

@aleksanb aleksanb force-pushed the check-for-empty-string branch from 1e1c733 to c5de7dc Compare March 14, 2025 16:13
@aleksanb
Copy link
Author

aleksanb commented Mar 14, 2025

Aha, pre-commit run ruff-format --all-files was the magic incantation i needed. Crazy that ruff tells me no changes are needed, and yet it formats when i run ruff-format.

@nijel
Copy link
Member

nijel commented Mar 14, 2025

Django main fails now because check has been renamed to condition, but that change doesn't happen until django 6.0.

Django main is tested against Django from Git. I don't mind dropping support for old Django versions if needed.

@aleksanb
Copy link
Author

Check was replaced with condition in 5.1 it seems, https://docs.djangoproject.com/en/5.1/ref/models/constraints/#django.db.models.CheckConstraint.condition, and 4.2 is still in LTS.
Unsure how to proceed, should i add an IF in there checking if the django version <6.0? But the migrations would need to differ as well.

@nijel
Copy link
Member

nijel commented Mar 14, 2025

That's unfortunate that it is impossible to write code which would work with both 4.2 and 6.0 as both will be supported at the same time by Django. For now, we can probably skip testing Django main (and replace it with 5.2b1), but we will need a solution for this later. That might be dropping support for Django < 5.1 once Django 5.0 is no longer supported.

@aleksanb aleksanb force-pushed the check-for-empty-string branch from c5de7dc to ca76b29 Compare March 17, 2025 16:14
@aleksanb
Copy link
Author

So it seems like the migrations get generated with check regardless of you use check or condition, so as long as we set up the Metaclass to switch based on django-version.

Pushed up a version with this now.

Any empty uids are surely erroneous, and better to have integrity errors
than having users start sharing accounts when logging in.
@aleksanb aleksanb force-pushed the check-for-empty-string branch from ca76b29 to d0182d1 Compare March 18, 2025 08:17
@nijel
Copy link
Member

nijel commented Mar 31, 2025

In #674 I'm dropping support for the oldest Django versions, but this will not help with this. Can you try discussing this compatibility issue at Django? Whether it is expected that the same code cannot work for both 4.2 and 6.0, while they will be supported at the same time.

@stianjensen
Copy link

I actually did ask a related question to this previously and their answer then was 'squash old migrations or edit them':
https://code.djangoproject.com/ticket/35234
That wasn't in context of how to publish a library supporting several Django versions in parallel, though, but seems like they broke migrations on purpose at least.

So I think the solution must be to add a version switch to the migration file as well, to make the kwargs to CheckConstraint dynamic.

@nijel
Copy link
Member

nijel commented Apr 1, 2025

I've posted this at https://forum.djangoproject.com/t/reusable-library-and-incompatible-django-core-changes/40097, let's see if we get some feedback. I'm not really sure if it's better to include additional code for compatibility with Django 4.2 or drop support for it (we can disregard 5.0 as it is out of support as of April 2025).

Comment on lines +16 to +19
constraint=models.CheckConstraint(
check=models.Q(("uid", ""), _negated=True),
name="user_social_auth_uid_required",
),

Choose a reason for hiding this comment

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

The same django.VERSION approach can be used here

Suggested change
constraint=models.CheckConstraint(
check=models.Q(("uid", ""), _negated=True),
name="user_social_auth_uid_required",
),
constraint=(
models.CheckConstraint(condition=~models.Q(uid=""), name="user_social_auth_uid_required")
if django.VERSION >= (5, 1)
else models.CheckConstraint(check=~models.Q(uid=""), name="user_social_auth_uid_required")
),

Comment on lines +47 to +50
if django.VERSION[0] < 6:
constraints = [models.CheckConstraint(check=~models.Q(uid=""), name="user_social_auth_uid_required")]
else:
constraints = [models.CheckConstraint(condition=~models.Q(uid=""), name="user_social_auth_uid_required")]

Choose a reason for hiding this comment

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

It would be better to favor the non-deprecated way instead

Suggested change
if django.VERSION[0] < 6:
constraints = [models.CheckConstraint(check=~models.Q(uid=""), name="user_social_auth_uid_required")]
else:
constraints = [models.CheckConstraint(condition=~models.Q(uid=""), name="user_social_auth_uid_required")]
if django.VERSION >= (5, 1):
constraints = [models.CheckConstraint(condition=~models.Q(uid=""), name="user_social_auth_uid_required")]
else:
constraints = [models.CheckConstraint(check=~models.Q(uid=""), name="user_social_auth_uid_required")]

nijel added a commit to nijel/social-app-django that referenced this pull request Apr 30, 2025
- Django 5.0 is no longer supported upstream.
- Django 4.2 is still LTS, but it is no longer possible to keep
  compatibility with upcoming Django 6 and 4.2 (see python-social-auth#557).
- Python 3.9 was supported only with Django 4.2.
@nijel
Copy link
Member

nijel commented Apr 30, 2025

As Django 5.0 is now out of support, I think it's time to drop support for 4.2 as well. LTS users can still use the older releases and it will be easier to move forward. See
#697

nijel added a commit that referenced this pull request May 5, 2025
- Django 5.0 is no longer supported upstream.
- Django 4.2 is still LTS, but it is no longer possible to keep
  compatibility with upcoming Django 6 and 4.2 (see #557).
- Python 3.9 was supported only with Django 4.2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants