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

[Django Upgrade] [ENG-4072] The Rest - Part 2 #10072

Conversation

cslzchen
Copy link
Contributor

@cslzchen cslzchen commented Sep 23, 2022

Purpose

See: https://www.notion.so/cos/The-rest-of-the-fixes-ec6b2b2f96e74a2b902a1d57d2a8fe6a

Tickets & PRs

Changes

  • Fix linked_by_nodes in node related counts test
  • Add alt fixtures to prevent both siblings & parent/child conflicts
  • Improve to_internal_value() in NodeRelationshipField
  • Revert "Fix an issue where get_node() is not availalbe from context view"
  • Update URLs for URL Validation tests with Django 3
  • Use a different schema to fix tests failure due to django3 upgrade
  • Fix handle_archive_fail and its tests
  • Fix visible contributor query
  • Rework refresh_from_db() to reload GFKs
  • Fix duplicate view_node permissions in node and instn view tests
  • Fix admin user view tests by using HTTP response headers object
  • Fix missing or insufficient permission test for admin preprints view

@cslzchen cslzchen changed the title [Django Upgrade] The Rest - Part 2 [Django Upgrade] [ENG-4072] The Rest - Part 2 Sep 23, 2022
@cslzchen cslzchen force-pushed the the-rest-of-the-fixes branch 3 times, most recently from 9af8993 to 5d9769a Compare September 28, 2022 17:01
@cslzchen cslzchen marked this pull request as ready for review October 3, 2022 07:07
osf/models/user.py Outdated Show resolved Hide resolved
cslzchen and others added 7 commits October 4, 2022 13:03
Co-authored-by: Jon Walz <jonmwalz@gmail.com>
Co-authored-by: John Tordoff <johnetordoff@gmail.com>
Co-authored-by: John Tordoff <johnetordoff@gmail.com>
This fixes a bug that is revealed by django3 upgrade: the object
returned from `self.context['view']` sometime doesn't have the
`.get_node()` method.
Copy link
Contributor Author

@cslzchen cslzchen left a comment

Choose a reason for hiding this comment

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

Finally, all CI tests passing 🎆

Comment on lines -106 to +107
resp = plain_view.as_view()(request, guid=preprint._id)
assert resp._headers['location'][1] == f'/accounts/login/?next=/preprints/{preprint._id}/'
with pytest.raises(PermissionDenied):
plain_view.as_view()(request, guid=preprint._id)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

When there is no permission or when permission is insufficient, django admin either redirect user to login page ( if the user is not authenticated) or raise the permission error (if authenticated). OSFUser set is_authenticated to True all the time and thus the correct behavior is raising exceptions.

Comment on lines -178 to +187
def test_correct_view_permissions(self, user, preprint, plain_view):
def test_valid_but_insufficient_view_permissions(self, user, preprint, plain_view):
view_permission = Permission.objects.get(codename='view_preprint')
user.user_permissions.add(view_permission)
user.save()

request = RequestFactory().get(reverse('preprints:preprint', kwargs={'guid': preprint._id}))
request.user = user

response = plain_view.as_view()(request, guid=preprint._id)
assert response.status_code == 302
with pytest.raises(PermissionDenied):
plain_view.as_view()(request, guid=preprint._id)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto, also renamed the test name to avoid confusion.

Comment on lines -211 to +214
self.view_node = Permission.objects.get(codename='view_node')
self.view_node = Permission.objects.filter(
codename='view_node',
content_type_id=ContentType.objects.get_for_model(AbstractNode).id
).first()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The issue of duplicate view_node permission is caused by the upgraded django added new default view_ permissions for node. Need to specify the content_type to point to the right one.

SELECT * FROM auth_permission WHERE codename='view_node';

+---+---------------------+---------------+---------+
|id |name                 |content_type_id|codename |
+---+---------------------+---------------+---------+
|73 |Can view node details|7              |view_node|
|515|Can view node        |14             |view_node|
+---+---------------------+---------------+---------+

Comment on lines -111 to -114
# Django's refresh_from_db does not uncache GFKs
for field in self._meta.private_fields:
if hasattr(field, 'cache_attr') and field.cache_attr in self.__dict__:
del self.__dict__[field.cache_attr]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • private_fields is not GFK though GFK seems to be in private_fields
  • Since Django 2.2, any cached relations are cleared from the reloaded instance

# behavior, our customization only reloads GFKs.
for f in self._meta._get_fields(reverse=False):
# Note: the following `if` condition is how django internally identifies GFK
if f.is_relation and f.many_to_one and not (hasattr(f.remote_field, 'model') and f.remote_field.model):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only refresh GFK, which is many_to_one and thus won't cause infinite loop.

Comment on lines -35 to +40
"http://-.~_!$&()*+,;=:%40:80%2f::::::@example.com":"should accept valid website with user but crazy username",
"http://-.~_!%24%26()*%2B%2C%3B%3D%3A%40%3A80%2F%3A%3A%3A%3A%3A%3A@example.com":"should accept valid website with user but crazy username",
"http://1337.net":"should accept valid website with just numbers in domain",
"http://definitelyawebsite.com?real=yes&page=definitely":"should accept valid website with query",
"http://a.b-c.de":"should accept valid website with dash",
"http://website.com:3000/?q=400":"should accept valid website with port and query",
"http://asd/asd@asd.com/":"should accept valid website with unusual username"
"http://asd%2Fasd@asd.com/":"should accept valid website with unusual username"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Django 3 is more strict on special characters encoding in URL and we should follow the spec.

Comment on lines 29 to 34
node = self.context['view'].get_node(node_id=node_id) if node_id else None
context_view = self.context['view']
if hasattr(context_view, 'get_node'):
node = self.context['view'].get_node(node_id=node_id) if node_id else None
else:
node = Node.load(node_id)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

After the upgrade, one test fails due to self.context['view'] returning an object not having .get_node. There is one concern of this fix since .get_node() checks permission and other stuff before loading the node. Might worth another look at the failed test.

Comment on lines +111 to +119
# Temporary alternative provider that supports `metaschema_open_ended` in `TestDraftRegistrationCreate`
# This provider is created to fix the first 3 tests in this test class due to test DB changes with the
# Django 3 Upgrade. A long-term solution is to create and/or use dedicated schemas for testing.
@pytest.fixture()
def provider_alt(self, metaschema_open_ended):
default_provider = RegistrationProvider.get_default()
default_provider.schemas.add(metaschema_open_ended)
default_provider.save()
return default_provider
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TestDraftRegistrationCreateWithNode inherits from TestDraftRegistrationCreate. Thus modifying the provider in the latter breaks TestDraftRegistrationCreate. In addition, some tests in TestDraftRegistrationCreateWithNode also relied on this version of metaschema_open_ended not in provider.

This issue is caused by our factories relies on test DB (i.e. adding the pk=1 schema when calling RegistrationFactory) instead of adding a schema with specified name and pinned version. Pre and post upgrade test DB are slightly different which breaks the tests.

Comment on lines +483 to 486
log_date = timezone.now()
project_private.deleted_date = log_date
project_private.deleted = log_date
project_private.is_deleted = True
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: somehow, when I tried project_private.remove_node(auth=Auth(user=user)), the node is not deleted though the method went through the process and returned True.

project_private.is_deleted = True
project_private.save()
registration.reload()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Must reload the registration, otherwise, registration.registered_from has the project_private node as active instead of deleted. If not, when WithdrawnRegistrationFactory is called using this registration, it old project_private overrides the deleted project_private and breaks the next assertion on linked_by_nodes

@cslzchen cslzchen merged commit c762700 into CenterForOpenScience:feature/django_upgrade Oct 10, 2022
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.

None yet

1 participant