-
Notifications
You must be signed in to change notification settings - Fork 5.3k
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
Add permissions for users #1106
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1106 +/- ##
==========================================
+ Coverage 63.43% 67.68% +4.24%
==========================================
Files 107 114 +7
Lines 5979 6140 +161
Branches 741 747 +6
==========================================
+ Hits 3793 4156 +363
+ Misses 2049 1812 -237
- Partials 137 172 +35
Continue to review full report at Codecov.
|
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'm reluctant to merge this as (1) there are no tests and (2) it does not support roles (called "groups" by Django) which I believe are the preferred way to group permissions.
saleor/core/permissions.py
Outdated
form_data = {} | ||
permissions = Permission.objects.filter(user=user) | ||
for permission in permissions: | ||
if str(permission.content_type) not in form_data: |
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 we used defaultdict
with form_data
we wouldn't need this if-else structure.
saleor/dashboard/staff/views.py
Outdated
from django.shortcuts import get_object_or_404 | ||
from django.template.response import TemplateResponse | ||
|
||
from saleor.core.permissions import get_user_permissions, update_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.
We use relative imports when importing local modules.
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.
A ok, this was autogenerated when i refactor in PyCharm
saleor/core/permissions.py
Outdated
MODELS_PERMISSIONS = (('view', 'View Products in Dashboard'), | ||
('edit', 'Edit Product in Dashboard')) | ||
|
||
PERMISSIONS = set([permission[0] for permission in MODELS_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.
Names MODELS_PERMISSIONS
and PERMISSIONS
are a bit confusing when you first read this, because it's hard to guess what is the purpose of having the two constants with similar names and the second being just the transformation of the first one. PERMISSIONS
is used only once in update_permissions
so maybe it should be a local variable. And if MODELS_PERMISSIONS
is meant to be imported across the project models then this could be the only constant.
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.
Yea i know this looks odd. I will change it to local variable.
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.
By the way, set comprehension has been a thing for at least five years 😄
.gitignore
Outdated
@@ -14,6 +14,7 @@ | |||
*.log | |||
*.pot | |||
*.pyc | |||
.envrc |
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.
Could you add such files to your global gitignore instead? Otherwise we'll end up with a list covering all existing tools and editors. 😄
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.
Ok sorry :)
saleor/dashboard/product/views.py
Outdated
@@ -17,6 +18,7 @@ | |||
|
|||
|
|||
@staff_member_required | |||
@permission_required('product.edit') |
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.
Instead of copy-pasting the same set of decorators we should have one for each unique set of permissions. Something like @product_manager_required
.
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.
This is a good idea when i implement Groups. I think.
saleor/product/models.py
Outdated
@@ -47,6 +48,8 @@ class Meta: | |||
verbose_name = pgettext_lazy('Category model', 'category') | |||
verbose_name_plural = pgettext_lazy('Category model', 'categories') | |||
app_label = 'product' | |||
permissions = (('view_category', 'Can View Category in Dashboard'), | |||
('edit_category', 'Can Edit Category in Dashboard')) |
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's the idea behind this title-like capitalization? Why have all permissions end in "in Dashboard"? Are we planning to introduce a second set of permissions for other use cases such as REST or GraphQL? If not the is_staff
field is responsible for the "in dashboard" part.
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.
As in Django documentation. This second value has to be "Human readible". I can simplyfi this to just "Edit"/"View" or "edit"/"view".
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.
Sorry, I meant the Way It is Capitalized.
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.
ok i will turn it off
saleor/dashboard/customer/views.py
Outdated
@@ -17,7 +17,8 @@ def customer_list(request): | |||
.select_related('default_billing_address', 'default_shipping_address') | |||
.annotate( | |||
num_orders=Count('orders', distinct=True), | |||
last_order=Max('orders', distinct=True))) | |||
last_order=Max('orders', distinct=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.
Why?
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.
As in commit message. Staff can be also a customer. Now we have a separate view with only staff members. If sb would like to see other user Order history, he will not have to search in two places.
- Customers - list of all users
- Staff members - list of users which are staff members
saleor/core/permissions.py
Outdated
q |= Q(content_type__app_label=app, content_type__model__in=models) | ||
q &= ~Q(content_type__app_label=app, codename__startswith='add_') | ||
q &= ~Q(content_type__app_label=app, codename__startswith='change_') | ||
q &= ~Q(content_type__app_label=app, codename__startswith='delete_') |
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.
This seems like a very inefficient way to fetch data. Why not match how has_perm
works and query for a list of known appname+codename pairs?
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.
This is used only to construct ModelForm when creating/editing a group.
This only excludes a standard django permissions which are created for every 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.
I understand how it works. The point I'm trying to make is that you could change the GROUP_PERMISSIONS_MODELS
setting to use the same syntax that has_perm
uses and end up with a much simpler query that did not require costly __startswith
lookups.
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.
By the way, is there a reason for GROUP_PERMISSIONS_MODELS
to live in settings
? I don't think it's likely to change unless you add more views.
Technical but important detail: please follow this guidelines for writing commit messages. |
saleor/core/permissions.py
Outdated
] | ||
|
||
|
||
def get_user_groups(user): |
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.
Do we need this function?
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.
No :)
saleor/dashboard/group/urls.py
Outdated
|
||
|
||
urlpatterns = [ | ||
url(r'^$', views.groups_list, name="group-list"), |
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.
Strings should be consistently enclosed in quotes or double quotes. In Saleor we tend to use single quotes in most cases.
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.
old habbits
saleor/dashboard/group/views.py
Outdated
@@ -0,0 +1,59 @@ | |||
from __future__ import unicode_literals | |||
|
|||
from django.shortcuts import redirect, get_object_or_404 |
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.
Imports should be sorted.
saleor/dashboard/group/views.py
Outdated
@staff_member_required | ||
def groups_list(request): | ||
groups = Group.objects.all() | ||
|
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.
This empty line is not necessary here. It's simple function and it doesn't need visual separation of logic.
saleor/dashboard/group/views.py
Outdated
@staff_member_required | ||
def group_create(request): | ||
form = GroupPermissionsForm(request.POST or 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.
Again, I would remove these empty lines.
@@ -35,7 +35,7 @@ | |||
{% endif %} | |||
{% for choice in field %} | |||
<p> | |||
<input id="{{ choice.id_for_label }}" name="{{ choice.name }}" class="filled-in" type="checkbox" value="{{ choice.choice_value }}" {% if choice.choice_value in choice.value %} checked="checked" {% endif %}> | |||
<input id="{{ choice.id_for_label }}" name="{{ choice.data.name }}" class="filled-in" type="checkbox" value="{{ choice.data.value }}" {% if choice.data.selected%} checked="checked" {% endif %}> |
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.
Missing space before '%' in {% if choice.data.selected%}
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.
Done
tests/conftest.py
Outdated
def staff_client(staff_user): | ||
"""A Django test client logged in as an staff member""" | ||
from django.test.client import Client | ||
client = Client() |
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.
Instead of creating the client here you could use a fixture just as in authorized_client
below.
tests/dashboard/test_permissions.py
Outdated
from saleor.dashboard.staff.forms import UserGroupForm | ||
|
||
|
||
def test_superuser_permissions(admin_user): |
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'm not sure that we need such tests. Superuser has all permission by default and this is granted by Django. We don't have test that Django works correctly:)
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.
Yea, first test. Checking if this works as i expect and i forget about it. Deleted
tests/dashboard/test_permissions.py
Outdated
assert admin_user.has_perm("product.edit_product") | ||
|
||
|
||
def test_superuser(admin_user): |
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 is the purpose of this test?
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.
Again just checking. Deleted
tests/dashboard/test_permissions.py
Outdated
|
||
|
||
def test_staff_list_view(admin_client): | ||
response = admin_client.get('/dashboard/staff/') |
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.
Again, you should always use reverse
for getting URLs.
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.
Ok
saleor/core/permissions.py
Outdated
for group_permission in GROUP_PERMISSIONS_MODELS: | ||
model_name = group_permission.split('.')[1] | ||
codenames.append('view_%s' % model_name) | ||
codenames.append('edit_%s' % model_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.
Why can't we list those explicitly in GROUP_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.
We can but the idea was to edit only one place when add new permission to view/edit sth and to filter out those default from django.
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.
You will still edit only one place. You'll just need to add two permissions there. What if we introduce a financial reporting feature that does not use an "edit" permission or we need to limit the ability to delete certain items?
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.
This was consulted with @elwoodxblues. I think we should discus this together.
We have to wait for PR #966 to have more sens. |
45f55f1
to
f148dcf
Compare
Deleted staff members from the customer list in dashboard. Added new templates for Staff section. Prepared siple form for future permission handling for staff members.
Permissions have to be unique for every model in django app.
Removed variable pk from update_permissions.
Test incomplete.
- Added new tests for accessing product list and product update. - Redesign get_permissions to be more readable. - Moved constant variable GROUP_PERMISSIONS_MODELS from settings.py to permissions.py. - Updated @permission_required for dashboard product views to match permissions from products models.
- Changed staff and group templates matching the rest of the site. - Add first tests using user with group.
- Add group delete functionality. View and templates ready. Tests in progress. - Change groups-list to group-list. - Change dashboard/groups to dashboard/group. - Add some tests of group and staff forms.
Refactor permissions.py with better names of variables and simplyfied logic. List of permissions now have name convention as User.has_perm().
Order permissions in permissions.py MODEL_PERMISSIONS.
f148dcf
to
5882a1d
Compare
Update dashboard.product.views.py with correct required permissions. Update dashboard/product/detail.html with correct required permissions.
Update templates in dashboard.products to hide buttons if user has no permission for respective action.
Add missing migration merge with changes from read-only PR.
Add updates in dashboard/category templates with required permissions.
Add updates in dashboard templates with required permissions.
Add pgettext_lazy for permissions description in Meta class of objects with model permissions.
Add list of permissions in group list in dashboard. Permissions are listed as <p class='list-item-desc">.
saleor/dashboard/group/views.py
Outdated
@staff_member_required | ||
def group_list(request): | ||
groups = [{'name': group, 'permissions': group.permissions.all()} | ||
for group in Group.objects.all()] |
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.
Shouldn't we prefetch permissions when querying Groups? It looks like accessing group.permissions.all()
will perform duplicated queries.
saleor/dashboard/product/views.py
Outdated
@@ -263,7 +279,23 @@ def stock_delete(request, product_pk, variant_pk, stock_pk): | |||
request, 'dashboard/product/stock/modal_confirm_delete.html', ctx) | |||
|
|||
|
|||
@require_http_methods(['POST']) | |||
@permission_required('product.edit_stock_location') | |||
def stock_bulk_delete(request, product_pk): |
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.
Is this view used? If I'm correct we have removed the ability for bulk deletions in PR with read-only views.
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.
Yea, during rebase i didn't delete any of the views so there might be some old present.
saleor/dashboard/product/views.py
Outdated
@@ -405,6 +443,24 @@ def variant_delete(request, product_pk, variant_pk): | |||
|
|||
|
|||
@staff_member_required | |||
@require_http_methods(['POST']) | |||
@permission_required('product.edit_product') | |||
def variants_bulk_delete(request, product_pk): |
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.
Same as above, I think this is not used.
@@ -0,0 +1,19 @@ | |||
# -*- coding: utf-8 -*- |
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.
This migration does the same what saleor/order/migrations/0018_auto_20170907_0909.py
does. Every new migration file makes the process of applying all migrations a little bit longer, so we should try to add only necessary migrations. I would revert these two migrations, remove them and recreate. This will result only in one migration file.
@@ -0,0 +1,27 @@ | |||
# -*- coding: utf-8 -*- |
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.
All product migrations from 0036
to this one could be replaced with one migration file.
templates/dashboard/base.html
Outdated
<li class="side-nav-section"> | ||
<p> | ||
{% trans "Configuration" context "Dashboard configuration" %} | ||
</p> | ||
<ul> | ||
<li class="{% block menu_staff_class %}{% endblock %}"> |
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.
"Staff members" and "Groups" sections should probably be visible only to superusers.
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.
Yea i have a question if we need some additional User model permissions for staff like the whole Configuration in Dashboard or do we just use 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.
Decided to cover every view and template behind superuser check.
Links to sections "Product types" and "Attributes" should also be hidden in template depending on permissions. "Site settings" should be visible only to superusers. I don't know about "Shipping methods", maybe we need separate permissions for them? |
- Add superuser_require decorator to dashboard views.py - Add @superuser_required decorators to every view from Configuration side menu in dashboard. - Add superuser check in dashboard/base.html for Configuration side menu. - Corrected migrations.
- Order template has "Add note" hidden behind permission edit_order. - Order view add_note requires permission edit_order. - Correct padding in group template. - Delete information about orders and addresses from staff detail. - Add redirect to group-list after group update.
Correct tests with changes in dashboard orders permissions.
Fix textarea issue from #1123.
@patrys I think that this PR is ready to merge. What do you think? |
One thing i missed. We talk about updating populatedb script with at least one group. |
Update populatedb script to create Group example "Products Manager" with permissions to view and edit products in dashboard. Script uses get_or_create to create Group only at the first time.
Delete blank lines in populatedb script.
Fix title trans context in group/list.html. Delete additional line break.
Fix query for Permission in permissions.py Fix query for staff_members in views.py Delete unused templatetags in modal_group_confirm_delete.
🎉 |
This closes #301
This also fixes #1123