-
Notifications
You must be signed in to change notification settings - Fork 104
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
Implement warnings #688
Implement warnings #688
Conversation
Pull Request Test Coverage Report for Build 3446239714
💛 - Coveralls |
src/chains/admin.py
Outdated
def get_readonly_fields( | ||
self, request: HttpRequest, obj: Optional[Chain] = None | ||
) -> List[str]: | ||
if request.user.groups.filter(name="support"): # type: ignore |
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.
I would prefer to use custom permissions instead of relying on the group, as in Django a group is only an aggregation of permissions: https://docs.djangoproject.com/en/4.1/topics/auth/customizing/#custom-permissions
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.
What about having a new table for the warning messages? ChainId + Message.
The endpoint can return the message from this table. The permission then can be done on the table level. Maybe it's easier and simpler this way wdyt?
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.
I would prefer not if it's possible to do it in another way, I like having a normalized database if possible
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.
I was thinking more about this field and in a way it seems that it's not really part of the chain metadata so that's why I thought about a separate table. Even though it's possible in a different way the Chain model itself already has quite some fields. But just an idea (nothing that would block the feature).
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.
I was thinking in having a separated table as well, it could be nice to keep an history of the changes as well, but I initially discarded it because it seems a bit overkill for the feature.
About the custom permission... it was my initial approach as well, but in this case we need to still grant edit permission over all Chain fields to superusers, and only over warning
field for a selected group of users. I thinking in creating an additional permission, something like can_change_all_chain_fields
and when a user doesn't have it, show all the fields as read-only except warning
. Do you see a simpler way here?
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.
I think I'm missing something, but why not using if request.user.has_perm("app.permission_name")
?
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.
Maybe it's me the one missing something, but we need to avoid support group from modifying any field except for warning
right? So it's kind of negative permission. I mean, all the other fields are the ones to restrict to some users (support), right?
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.
I see it more like:
- By default users don't have any permission unless you make them superusers or give them permissions to every table individually.
support
group will grant theedit chains
andchange_only_warning
to all his members.- To edit the chains table users will need the
edit chains
permission - People with
change_only_warning
permission will be able to modify onlywarning
field, soif request.user.has_perm(
change_only_warning):
every field but warning will be read_only
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.
@Uxio0 thanks for your feedback and guidance on this 🙂. I was doing some tests and it seems that when creating a new permission (change_only_warning
in this case), that permission is automatically inherited by superuser(s). So, in this case, all fields are converted to read_only
for the superusers as well (except warning
of course). 🤔 I see two options here:
- Rely on the group.
- Have an additional check to verify the user is not a
superadmin
before marking the fields as read-only.
if (
request.user.has_perm("change_only_warning")
and not request.user.is_superuser
):
I think the second option is better, what do you think?
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.
I implemented the second option, of course if you see any concerns with it let me know 🙂
src/chains/models.py
Outdated
@@ -119,6 +119,7 @@ class RpcAuthentication(models.TextChoices): | |||
recommended_master_copy_version = models.CharField( | |||
max_length=255, validators=[sem_ver_validator] | |||
) | |||
warning = models.CharField(max_length=255, blank=True, null=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.
Maybe a TextField
?
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.
Yes, thanks! This is something I forgot to annotate in the PR, but I was doubting about it as well 👍
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.
Changed in 02e17b2
|
||
def create_support_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None: | ||
support_group, created = Group.objects.get_or_create(name="support") | ||
permission = Permission.objects.filter(codename="change_chain") |
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.
Where is this permission defined? It should be on Meta
for the Chain
model: https://docs.djangoproject.com/en/4.1/topics/auth/customizing/#custom-permissions
support_group, created = Group.objects.get_or_create(name="support") | ||
permission = Permission.objects.filter(codename="change_chain") | ||
if permission: | ||
support_group.permissions.add(permission[0]) |
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.
If you only expect to get one permission you can do Permission.objects.get(codename="change_chain")
def create_support_group(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None: | ||
support_group, created = Group.objects.get_or_create(name="support") | ||
permission = Permission.objects.filter(codename="change_chain") | ||
if permission: |
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.
Permission must exist we shouldn't check this
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.
I was getting an error when running tests due to this, that's why I included the condition. Maybe it's related with not having the permission defined in Meta
as you said in the previous comment...
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.
That's it. Permission should be created in a migration when adding it to Meta
, and you can use that migration to add it to the group. Also, if possible, do everything in the same migration
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.
@Uxio0 I was testing a lot trying to understand why the migration was failing (because the permission wasn't found), I think the reason why we need Permission.objects.get_or_create
here instead of simply Permission.objects.get
is because the permissions data is not inferred/written to the database until all migrations are executed (using the post_migrate signal if I'm not wrong).
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.
I think this issue is related. This also explains why test were failing in my local setup (I understand the whole list of migrations gets executed against a test database when running the tests).
@@ -129,6 +130,9 @@ def get_disabled_wallets(self) -> QuerySet["Wallet"]: | |||
def __str__(self) -> str: | |||
return f"{self.name} | chain_id={self.id}" | |||
|
|||
class Meta: | |||
permissions = [("change_only_warning", "Can change only warning")] |
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.
I would name it change_warning
with the description: Can change the chain warning
.
I think "only" might be redundant if the permission is tied to this field.
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
is included in the name because of the nature of this permission. This permission will imply two things for an user:
- The user can edit the
warning
field. - The user cannot edit any other
Chain
field, i.e. the rest of the fields will be marked as read-only.
I know it is not very intuitive, but we agreed on taking this approach to avoid using a third party dependency which would let us define field-level permissions. Even using that dependency, I suspect the outcome wouldn't be very intuitive either, because our goal is not to grant new permissions, but preventing some users to access some fields.
@@ -118,6 +118,7 @@ class ChainSerializer(serializers.ModelSerializer[Chain]): | |||
ens_registry_address = EthereumAddressField() | |||
disabled_wallets = serializers.SerializerMethodField() | |||
features = serializers.SerializerMethodField() | |||
warning = serializers.CharField() |
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.
Was there any decision on the name of this field? We should be aligned with the clients regarding this.
Can this field be used to have just some information out (which is not a warning) for example?
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.
It is named warning
because of the issue this change is related: New "warning" text field in chain configs 🙂 But for sure we can sync about of this.
) -> List[str]: | ||
if ( | ||
request.user.has_perm("chains.change_only_warning") | ||
and not request.user.is_superuser |
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.
Why don't we allow a superuser to change this field? (even without the permission)
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.
(Please refer to the previous comment for reference)
This function (get_readonly_fields
) returns the read-only fields, so for an user which have the change_only_warning
and is not a superuser, will return all the Chain
fields except warning
. For superusers, it will return []
, so all the fields are editable. In short, we allow superusers to change this field.
(I know this code is not super intuitive to read, please let me know if you find a better approach).
src/chains/tests/test_views.py
Outdated
@@ -195,6 +196,7 @@ def test_json_payload_format(self) -> None: | |||
"recommendedMasterCopyVersion": chain.recommended_master_copy_version, | |||
"disabledWallets": [], | |||
"features": [], | |||
"warning": None, |
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.
We are missing a test if the warning is set.
The factory method should also be updated
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.
Added in 02e17b2 👍🏻
I'm drafting this PR since there is an ongoing discussion in Notion. |
I'm closing this PR because the source issue is also closed now. Please reach out if you want to discuss this feature further 🙂 |
Relates to #675
This PR aims to create a new
warning
field for eachChain
. This newwarning
field is nullable and should be updatable by superusers, and also for support collaborators. That's why a newsupport
group is created. The idea is to make allChain
fields read-only for support collaborators, exceptwarning
, so this group can only edit thewarning
field.Using this approach we avoid the usage of third party libraries or external dependencies. It's implemented by just using Django features. Feedback is welcomed! 🙂