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
[Django Upgrade] [ENG-4072] The Rest - Part 2 #10072
Conversation
9af8993
to
5d9769a
Compare
Co-authored-by: John Tordoff <johnetordoff@gmail.com>
Co-authored-by: John Tordoff <johnetordoff@gmail.com>
d43ca7f
to
f47df68
Compare
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 reverts commit f109ffc.
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.
339099a
to
227ee3a
Compare
There was a problem hiding this 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 🎆
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) |
There was a problem hiding this comment.
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.
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) |
There was a problem hiding this comment.
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.
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() |
There was a problem hiding this comment.
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|
+---+---------------------+---------------+---------+
# 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] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private_fields
is notGFK
thoughGFK
seems to be inprivate_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): |
There was a problem hiding this comment.
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.
"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" |
There was a problem hiding this comment.
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.
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) |
There was a problem hiding this comment.
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.
# 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 |
There was a problem hiding this comment.
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.
log_date = timezone.now() | ||
project_private.deleted_date = log_date | ||
project_private.deleted = log_date | ||
project_private.is_deleted = True |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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
Purpose
See: https://www.notion.so/cos/The-rest-of-the-fixes-ec6b2b2f96e74a2b902a1d57d2a8fe6a
Tickets & PRs
Changes