Merge pull request #1024 from SchrodingersGat/part-permissions

Permissions
This commit is contained in:
Oliver 2020-10-06 18:29:05 +11:00 committed by GitHub
commit 606c62078f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1187 additions and 642 deletions

View File

@ -30,6 +30,8 @@ class InfoView(AjaxView):
Use to confirm that the server is running, etc. Use to confirm that the server is running, etc.
""" """
permission_classes = [permissions.AllowAny]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
data = { data = {

View File

@ -17,3 +17,43 @@ def status_codes(request):
'BuildStatus': BuildStatus, 'BuildStatus': BuildStatus,
'StockStatus': StockStatus, 'StockStatus': StockStatus,
} }
def user_roles(request):
"""
Return a map of the current roles assigned to the user.
Roles are denoted by their simple names, and then the permission type.
Permissions can be access as follows:
- roles.part.view
- roles.build.delete
Each value will return a boolean True / False
"""
user = request.user
roles = {
}
for group in user.groups.all():
for rule in group.rule_sets.all():
# Ensure the role name is in the dict
if rule.name not in roles:
roles[rule.name] = {
'view': user.is_superuser,
'add': user.is_superuser,
'change': user.is_superuser,
'delete': user.is_superuser
}
# Roles are additive across groups
roles[rule.name]['view'] |= rule.can_view
roles[rule.name]['add'] |= rule.can_add
roles[rule.name]['change'] |= rule.can_change
roles[rule.name]['delete'] |= rule.can_delete
return {'roles': roles}

View File

@ -15,6 +15,8 @@ from django.http import StreamingHttpResponse
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.contrib.auth.models import Permission
import InvenTree.version import InvenTree.version
from .settings import MEDIA_URL, STATIC_URL from .settings import MEDIA_URL, STATIC_URL
@ -441,3 +443,21 @@ def validateFilterString(value):
results[k] = v results[k] = v
return results return results
def addUserPermission(user, permission):
"""
Shortcut function for adding a certain permission to a user.
"""
perm = Permission.objects.get(codename=permission)
user.user_permissions.add(perm)
def addUserPermissions(user, permissions):
"""
Shortcut function for adding multiple permissions to a user.
"""
for permission in permissions:
addUserPermission(user, permission)

View File

@ -210,6 +210,7 @@ TEMPLATES = [
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'InvenTree.context.status_codes', 'InvenTree.context.status_codes',
'InvenTree.context.user_roles',
], ],
}, },
}, },
@ -231,6 +232,10 @@ REST_FRAMEWORK = {
'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.TokenAuthentication',
), ),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.DjangoModelPermissions',
),
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
} }

View File

@ -22,6 +22,7 @@ from django.views.generic.base import TemplateView
from part.models import Part, PartCategory from part.models import Part, PartCategory
from stock.models import StockLocation, StockItem from stock.models import StockLocation, StockItem
from common.models import InvenTreeSetting, ColorTheme from common.models import InvenTreeSetting, ColorTheme
from users.models import check_user_role
from .forms import DeleteForm, EditUserForm, SetPasswordForm, ColorThemeSelectForm from .forms import DeleteForm, EditUserForm, SetPasswordForm, ColorThemeSelectForm
from .helpers import str2bool from .helpers import str2bool
@ -107,31 +108,66 @@ class TreeSerializer(views.APIView):
return JsonResponse(response, safe=False) return JsonResponse(response, safe=False)
class AjaxMixin(PermissionRequiredMixin): class InvenTreeRoleMixin(PermissionRequiredMixin):
"""
Permission class based on user roles, not user 'permissions'.
To specify which role is required for the mixin,
set the class attribute 'role_required' to something like the following:
role_required = 'part.add'
role_required = [
'part.change',
'build.add',
]
"""
# By default, no roles are required
# Roles must be specified
role_required = None
def has_permission(self):
"""
Determine if the current user
"""
roles_required = []
if type(self.role_required) is str:
roles_required.append(self.role_required)
elif type(self.role_required) in [list, tuple]:
roles_required = self.role_required
user = self.request.user
# Superuser can have any permissions they desire
if user.is_superuser:
return True
for required in roles_required:
(role, permission) = required.split('.')
# Return False if the user does not have *any* of the required roles
if not check_user_role(user, role, permission):
return False
# We did not fail any required checks
return True
class AjaxMixin(InvenTreeRoleMixin):
""" AjaxMixin provides basic functionality for rendering a Django form to JSON. """ AjaxMixin provides basic functionality for rendering a Django form to JSON.
Handles jsonResponse rendering, and adds extra data for the modal forms to process Handles jsonResponse rendering, and adds extra data for the modal forms to process
on the client side. on the client side.
Any view which inherits the AjaxMixin will need Any view which inherits the AjaxMixin will need
correct permissions set using the 'permission_required' attribute correct permissions set using the 'role_required' attribute
""" """
# By default, allow *any* permissions # By default, allow *any* role
permission_required = '*' role_required = None
def has_permission(self):
"""
Override the default behaviour of has_permission from PermissionRequiredMixin.
Basically, if permission_required attribute = '*',
no permissions are actually required!
"""
if self.permission_required == '*':
return True
else:
return super().has_permission()
# By default, point to the modal_form template # By default, point to the modal_form template
# (this can be overridden by a child class) # (this can be overridden by a child class)

View File

@ -7,7 +7,7 @@ from __future__ import unicode_literals
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters from rest_framework import filters
from rest_framework import generics, permissions from rest_framework import generics
from django.conf.urls import url, include from django.conf.urls import url, include
@ -28,10 +28,6 @@ class BuildList(generics.ListCreateAPIView):
queryset = Build.objects.all() queryset = Build.objects.all()
serializer_class = BuildSerializer serializer_class = BuildSerializer
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
@ -99,10 +95,6 @@ class BuildDetail(generics.RetrieveUpdateAPIView):
queryset = Build.objects.all() queryset = Build.objects.all()
serializer_class = BuildSerializer serializer_class = BuildSerializer
permission_classes = [
permissions.IsAuthenticated,
]
class BuildItemList(generics.ListCreateAPIView): class BuildItemList(generics.ListCreateAPIView):
""" API endpoint for accessing a list of BuildItem objects """ API endpoint for accessing a list of BuildItem objects
@ -137,10 +129,6 @@ class BuildItemList(generics.ListCreateAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
] ]

View File

@ -35,7 +35,7 @@ src="{% static 'img/blank_image.png' %}"
<hr> <hr>
<h4> <h4>
{{ build.quantity }} x {{ build.part.full_name }} {{ build.quantity }} x {{ build.part.full_name }}
{% if user.is_staff and perms.build.change_build %} {% if user.is_staff and roles.build.change %}
<a href="{% url 'admin:build_build_change' build.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a> <a href="{% url 'admin:build_build_change' build.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
{% endif %} {% endif %}
</h4> </h4>

View File

@ -7,7 +7,7 @@ from __future__ import unicode_literals
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters from rest_framework import filters
from rest_framework import generics, permissions from rest_framework import generics
from django.conf.urls import url, include from django.conf.urls import url, include
from django.db.models import Q from django.db.models import Q
@ -40,10 +40,6 @@ class CompanyList(generics.ListCreateAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
@ -82,10 +78,6 @@ class CompanyDetail(generics.RetrieveUpdateDestroyAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated,
]
class SupplierPartList(generics.ListCreateAPIView): class SupplierPartList(generics.ListCreateAPIView):
""" API endpoint for list view of SupplierPart object """ API endpoint for list view of SupplierPart object
@ -170,10 +162,6 @@ class SupplierPartList(generics.ListCreateAPIView):
serializer_class = SupplierPartSerializer serializer_class = SupplierPartSerializer
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
@ -202,7 +190,6 @@ class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = SupplierPart.objects.all() queryset = SupplierPart.objects.all()
serializer_class = SupplierPartSerializer serializer_class = SupplierPartSerializer
permission_classes = (permissions.IsAuthenticated,)
read_only_fields = [ read_only_fields = [
] ]
@ -218,10 +205,6 @@ class SupplierPriceBreakList(generics.ListCreateAPIView):
queryset = SupplierPriceBreak.objects.all() queryset = SupplierPriceBreak.objects.all()
serializer_class = SupplierPriceBreakSerializer serializer_class = SupplierPriceBreakSerializer
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
] ]

View File

@ -23,7 +23,7 @@ InvenTree | {% trans "Company" %} - {{ company.name }}
<hr> <hr>
<h4> <h4>
{{ company.name }} {{ company.name }}
{% if user.is_staff and perms.company.change_company %} {% if user.is_staff and roles.company.change %}
<a href="{% url 'admin:company_company_change' company.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a> <a href="{% url 'admin:company_company_change' company.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
{% endif %} {% endif %}
</h4> </h4>

View File

@ -3,6 +3,8 @@ from rest_framework import status
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from InvenTree.helpers import addUserPermissions
from .models import Company from .models import Company
@ -14,7 +16,16 @@ class CompanyTest(APITestCase):
def setUp(self): def setUp(self):
# Create a user for auth # Create a user for auth
User = get_user_model() User = get_user_model()
User.objects.create_user('testuser', 'test@testing.com', 'password') self.user = User.objects.create_user('testuser', 'test@testing.com', 'password')
perms = [
'view_company',
'change_company',
'add_company',
]
addUserPermissions(self.user, perms)
self.client.login(username='testuser', password='password') self.client.login(username='testuser', password='password')
Company.objects.create(name='ACME', description='Supplier', is_customer=False, is_supplier=True) Company.objects.create(name='ACME', description='Supplier', is_customer=False, is_supplier=True)

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-04 14:02+0000\n" "POT-Creation-Date: 2020-10-05 13:20+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -90,7 +90,7 @@ msgstr ""
msgid "User" msgid "User"
msgstr "" msgstr ""
#: InvenTree/models.py:106 part/templates/part/params.html:20 #: InvenTree/models.py:106 part/templates/part/params.html:22
#: templates/js/part.html:81 #: templates/js/part.html:81
msgid "Name" msgid "Name"
msgstr "" msgstr ""
@ -99,19 +99,19 @@ msgstr ""
msgid "Description (optional)" msgid "Description (optional)"
msgstr "" msgstr ""
#: InvenTree/settings.py:342 #: InvenTree/settings.py:343
msgid "English" msgid "English"
msgstr "" msgstr ""
#: InvenTree/settings.py:343 #: InvenTree/settings.py:344
msgid "German" msgid "German"
msgstr "" msgstr ""
#: InvenTree/settings.py:344 #: InvenTree/settings.py:345
msgid "French" msgid "French"
msgstr "" msgstr ""
#: InvenTree/settings.py:345 #: InvenTree/settings.py:346
msgid "Polish" msgid "Polish"
msgstr "" msgstr ""
@ -325,7 +325,7 @@ msgstr ""
msgid "Number of parts to build" msgid "Number of parts to build"
msgstr "" msgstr ""
#: build/models.py:128 part/templates/part/part_base.html:145 #: build/models.py:128 part/templates/part/part_base.html:155
msgid "Build Status" msgid "Build Status"
msgstr "" msgstr ""
@ -344,7 +344,7 @@ msgstr ""
#: build/models.py:155 build/templates/build/detail.html:55 #: build/models.py:155 build/templates/build/detail.html:55
#: company/templates/company/supplier_part_base.html:60 #: company/templates/company/supplier_part_base.html:60
#: company/templates/company/supplier_part_detail.html:24 #: company/templates/company/supplier_part_detail.html:24
#: part/templates/part/detail.html:80 part/templates/part/part_base.html:92 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102
#: stock/models.py:381 stock/templates/stock/item_base.html:244 #: stock/models.py:381 stock/templates/stock/item_base.html:244
msgid "External Link" msgid "External Link"
msgstr "" msgstr ""
@ -356,7 +356,7 @@ msgstr ""
#: build/models.py:160 build/templates/build/tabs.html:14 company/models.py:310 #: build/models.py:160 build/templates/build/tabs.html:14 company/models.py:310
#: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:15 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:15
#: order/templates/order/purchase_order_detail.html:202 #: order/templates/order/purchase_order_detail.html:202
#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:67 #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:70
#: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 #: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453
#: stock/models.py:1404 stock/templates/stock/tabs.html:26 #: stock/models.py:1404 stock/templates/stock/tabs.html:26
#: templates/js/barcode.html:391 templates/js/bom.html:223 #: templates/js/barcode.html:391 templates/js/bom.html:223
@ -404,7 +404,7 @@ msgstr ""
#: build/templates/build/allocate.html:17 #: build/templates/build/allocate.html:17
#: company/templates/company/detail_part.html:18 order/views.py:779 #: company/templates/company/detail_part.html:18 order/views.py:779
#: part/templates/part/category.html:112 #: part/templates/part/category.html:122
msgid "Order Parts" msgid "Order Parts"
msgstr "" msgstr ""
@ -420,7 +420,7 @@ msgstr ""
msgid "Unallocate" msgid "Unallocate"
msgstr "" msgstr ""
#: build/templates/build/allocate.html:87 templates/stock_table.html:9 #: build/templates/build/allocate.html:87 templates/stock_table.html:10
msgid "New Stock Item" msgid "New Stock Item"
msgstr "" msgstr ""
@ -548,7 +548,7 @@ msgstr ""
#: build/templates/build/build_base.html:34 #: build/templates/build/build_base.html:34
#: build/templates/build/complete.html:6 #: build/templates/build/complete.html:6
#: stock/templates/stock/item_base.html:223 templates/js/build.html:39 #: stock/templates/stock/item_base.html:223 templates/js/build.html:39
#: templates/navbar.html:20 #: templates/navbar.html:25
msgid "Build" msgid "Build"
msgstr "" msgstr ""
@ -687,7 +687,7 @@ msgstr ""
#: build/templates/build/index.html:6 build/templates/build/index.html:14 #: build/templates/build/index.html:6 build/templates/build/index.html:14
#: order/templates/order/so_builds.html:11 order/templates/order/so_tabs.html:9 #: order/templates/order/so_builds.html:11 order/templates/order/so_tabs.html:9
#: part/templates/part/tabs.html:30 #: part/templates/part/tabs.html:31 users/models.py:30
msgid "Build Orders" msgid "Build Orders"
msgstr "" msgstr ""
@ -709,7 +709,7 @@ msgstr ""
#: build/templates/build/notes.html:33 company/templates/company/notes.html:30 #: build/templates/build/notes.html:33 company/templates/company/notes.html:30
#: order/templates/order/order_notes.html:32 #: order/templates/order/order_notes.html:32
#: order/templates/order/sales_order_notes.html:37 #: order/templates/order/sales_order_notes.html:37
#: part/templates/part/notes.html:32 stock/templates/stock/item_notes.html:33 #: part/templates/part/notes.html:33 stock/templates/stock/item_notes.html:33
msgid "Edit notes" msgid "Edit notes"
msgstr "" msgstr ""
@ -1044,13 +1044,13 @@ msgid "New Supplier Part"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:15 #: company/templates/company/detail_part.html:15
#: part/templates/part/category.html:109 part/templates/part/supplier.html:15 #: part/templates/part/category.html:117 part/templates/part/supplier.html:15
#: templates/stock_table.html:11 #: templates/stock_table.html:14
msgid "Options" msgid "Options"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:18 #: company/templates/company/detail_part.html:18
#: part/templates/part/category.html:112 #: part/templates/part/category.html:122
msgid "Order parts" msgid "Order parts"
msgstr "" msgstr ""
@ -1063,7 +1063,7 @@ msgid "Delete Parts"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:43 #: company/templates/company/detail_part.html:43
#: part/templates/part/category.html:107 templates/js/stock.html:791 #: part/templates/part/category.html:114 templates/js/stock.html:791
msgid "New Part" msgid "New Part"
msgstr "" msgstr ""
@ -1095,7 +1095,7 @@ msgstr ""
#: company/templates/company/detail_stock.html:35 #: company/templates/company/detail_stock.html:35
#: company/templates/company/supplier_part_stock.html:33 #: company/templates/company/supplier_part_stock.html:33
#: part/templates/part/category.html:106 part/templates/part/category.html:113 #: part/templates/part/category.html:112 part/templates/part/category.html:123
#: part/templates/part/stock.html:51 templates/stock_table.html:6 #: part/templates/part/stock.html:51 templates/stock_table.html:6
msgid "Export" msgid "Export"
msgstr "" msgstr ""
@ -1117,8 +1117,8 @@ msgstr ""
#: company/templates/company/tabs.html:17 #: company/templates/company/tabs.html:17
#: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:7
#: order/templates/order/purchase_orders.html:12 #: order/templates/order/purchase_orders.html:12
#: part/templates/part/orders.html:9 part/templates/part/tabs.html:45 #: part/templates/part/orders.html:9 part/templates/part/tabs.html:48
#: templates/navbar.html:26 #: templates/navbar.html:33 users/models.py:31
msgid "Purchase Orders" msgid "Purchase Orders"
msgstr "" msgstr ""
@ -1136,8 +1136,8 @@ msgstr ""
#: company/templates/company/tabs.html:22 #: company/templates/company/tabs.html:22
#: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:7
#: order/templates/order/sales_orders.html:12 #: order/templates/order/sales_orders.html:12
#: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:53 #: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56
#: templates/navbar.html:33 #: templates/navbar.html:42 users/models.py:32
msgid "Sales Orders" msgid "Sales Orders"
msgstr "" msgstr ""
@ -1158,7 +1158,7 @@ msgid "Supplier Part"
msgstr "" msgstr ""
#: company/templates/company/supplier_part_base.html:23 #: company/templates/company/supplier_part_base.html:23
#: part/templates/part/orders.html:14 #: part/templates/part/orders.html:14 part/templates/part/part_base.html:66
msgid "Order part" msgid "Order part"
msgstr "" msgstr ""
@ -1205,7 +1205,7 @@ msgid "Pricing Information"
msgstr "" msgstr ""
#: company/templates/company/supplier_part_pricing.html:15 company/views.py:399 #: company/templates/company/supplier_part_pricing.html:15 company/views.py:399
#: part/templates/part/sale_prices.html:13 part/views.py:2149 #: part/templates/part/sale_prices.html:13 part/views.py:2226
msgid "Add Price Break" msgid "Add Price Break"
msgstr "" msgstr ""
@ -1241,7 +1241,7 @@ msgstr ""
#: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18
#: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155
#: templates/js/part.html:124 templates/js/part.html:372 #: templates/js/part.html:124 templates/js/part.html:372
#: templates/js/stock.html:452 templates/navbar.html:19 #: templates/js/stock.html:452 templates/navbar.html:22 users/models.py:29
msgid "Stock" msgid "Stock"
msgstr "" msgstr ""
@ -1251,22 +1251,22 @@ msgstr ""
#: company/templates/company/tabs.html:9 #: company/templates/company/tabs.html:9
#: order/templates/order/receive_parts.html:14 part/models.py:294 #: order/templates/order/receive_parts.html:14 part/models.py:294
#: part/templates/part/cat_link.html:7 part/templates/part/category.html:88 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94
#: part/templates/part/category_tabs.html:6 templates/navbar.html:18 #: part/templates/part/category_tabs.html:6 templates/navbar.html:19
#: templates/stats.html:8 templates/stats.html:17 #: templates/stats.html:8 templates/stats.html:17 users/models.py:28
msgid "Parts" msgid "Parts"
msgstr "" msgstr ""
#: company/views.py:50 part/templates/part/tabs.html:39 #: company/views.py:50 part/templates/part/tabs.html:42
#: templates/navbar.html:24 #: templates/navbar.html:31
msgid "Suppliers" msgid "Suppliers"
msgstr "" msgstr ""
#: company/views.py:57 templates/navbar.html:25 #: company/views.py:57 templates/navbar.html:32
msgid "Manufacturers" msgid "Manufacturers"
msgstr "" msgstr ""
#: company/views.py:64 templates/navbar.html:32 #: company/views.py:64 templates/navbar.html:41
msgid "Customers" msgid "Customers"
msgstr "" msgstr ""
@ -1330,15 +1330,15 @@ msgstr ""
msgid "Delete Supplier Part" msgid "Delete Supplier Part"
msgstr "" msgstr ""
#: company/views.py:404 part/views.py:2153 #: company/views.py:404 part/views.py:2232
msgid "Added new price break" msgid "Added new price break"
msgstr "" msgstr ""
#: company/views.py:441 part/views.py:2198 #: company/views.py:441 part/views.py:2277
msgid "Edit Price Break" msgid "Edit Price Break"
msgstr "" msgstr ""
#: company/views.py:456 part/views.py:2212 #: company/views.py:456 part/views.py:2293
msgid "Delete Price Break" msgid "Delete Price Break"
msgstr "" msgstr ""
@ -1431,7 +1431,7 @@ msgstr ""
msgid "Date order was completed" msgid "Date order was completed"
msgstr "" msgstr ""
#: order/models.py:185 order/models.py:259 part/views.py:1304 #: order/models.py:185 order/models.py:259 part/views.py:1343
#: stock/models.py:241 stock/models.py:805 #: stock/models.py:241 stock/models.py:805
msgid "Quantity must be greater than zero" msgid "Quantity must be greater than zero"
msgstr "" msgstr ""
@ -1600,7 +1600,7 @@ msgid "Purchase Order Attachments"
msgstr "" msgstr ""
#: order/templates/order/po_tabs.html:8 order/templates/order/so_tabs.html:16 #: order/templates/order/po_tabs.html:8 order/templates/order/so_tabs.html:16
#: part/templates/part/tabs.html:64 stock/templates/stock/tabs.html:32 #: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32
msgid "Attachments" msgid "Attachments"
msgstr "" msgstr ""
@ -1616,7 +1616,7 @@ msgstr ""
#: order/templates/order/purchase_order_detail.html:38 #: order/templates/order/purchase_order_detail.html:38
#: order/templates/order/purchase_order_detail.html:118 #: order/templates/order/purchase_order_detail.html:118
#: part/templates/part/category.html:161 part/templates/part/category.html:202 #: part/templates/part/category.html:171 part/templates/part/category.html:213
#: templates/js/stock.html:803 #: templates/js/stock.html:803
msgid "New Location" msgid "New Location"
msgstr "" msgstr ""
@ -1658,7 +1658,7 @@ msgid "Select parts to receive against this order"
msgstr "" msgstr ""
#: order/templates/order/receive_parts.html:21 #: order/templates/order/receive_parts.html:21
#: part/templates/part/part_base.html:135 templates/js/part.html:388 #: part/templates/part/part_base.html:145 templates/js/part.html:388
msgid "On Order" msgid "On Order"
msgstr "" msgstr ""
@ -1750,7 +1750,7 @@ msgstr ""
msgid "Add Purchase Order Attachment" msgid "Add Purchase Order Attachment"
msgstr "" msgstr ""
#: order/views.py:102 order/views.py:149 part/views.py:86 stock/views.py:167 #: order/views.py:102 order/views.py:149 part/views.py:90 stock/views.py:167
msgid "Added attachment" msgid "Added attachment"
msgstr "" msgstr ""
@ -1890,12 +1890,12 @@ msgstr ""
msgid "Remove allocation" msgid "Remove allocation"
msgstr "" msgstr ""
#: part/bom.py:138 part/templates/part/category.html:55 #: part/bom.py:138 part/templates/part/category.html:61
#: part/templates/part/detail.html:87 #: part/templates/part/detail.html:87
msgid "Default Location" msgid "Default Location"
msgstr "" msgstr ""
#: part/bom.py:139 part/templates/part/part_base.html:108 #: part/bom.py:139 part/templates/part/part_base.html:118
msgid "Available Stock" msgid "Available Stock"
msgstr "" msgstr ""
@ -2013,7 +2013,7 @@ msgid "Part Category"
msgstr "" msgstr ""
#: part/models.py:76 part/templates/part/category.html:18 #: part/models.py:76 part/templates/part/category.html:18
#: part/templates/part/category.html:83 templates/stats.html:12 #: part/templates/part/category.html:89 templates/stats.html:12
msgid "Part Categories" msgid "Part Categories"
msgstr "" msgstr ""
@ -2226,7 +2226,7 @@ msgstr ""
msgid "BOM line checksum" msgid "BOM line checksum"
msgstr "" msgstr ""
#: part/models.py:1612 part/views.py:1310 part/views.py:1362 #: part/models.py:1612 part/views.py:1349 part/views.py:1401
#: stock/models.py:231 #: stock/models.py:231
msgid "Quantity must be integer value for trackable parts" msgid "Quantity must be integer value for trackable parts"
msgstr "" msgstr ""
@ -2285,23 +2285,23 @@ msgstr ""
msgid "Finish Editing" msgid "Finish Editing"
msgstr "" msgstr ""
#: part/templates/part/bom.html:42 #: part/templates/part/bom.html:43
msgid "Edit BOM" msgid "Edit BOM"
msgstr "" msgstr ""
#: part/templates/part/bom.html:44 #: part/templates/part/bom.html:45
msgid "Validate Bill of Materials" msgid "Validate Bill of Materials"
msgstr "" msgstr ""
#: part/templates/part/bom.html:46 part/views.py:1597 #: part/templates/part/bom.html:48 part/views.py:1640
msgid "Export Bill of Materials" msgid "Export Bill of Materials"
msgstr "" msgstr ""
#: part/templates/part/bom.html:101 #: part/templates/part/bom.html:103
msgid "Delete selected BOM items?" msgid "Delete selected BOM items?"
msgstr "" msgstr ""
#: part/templates/part/bom.html:102 #: part/templates/part/bom.html:104
msgid "All selected BOM items will be deleted" msgid "All selected BOM items will be deleted"
msgstr "" msgstr ""
@ -2373,83 +2373,91 @@ msgstr ""
msgid "Each part must already exist in the database" msgid "Each part must already exist in the database"
msgstr "" msgstr ""
#: part/templates/part/build.html:8
msgid "Part Builds"
msgstr ""
#: part/templates/part/build.html:14
msgid "Start New Build"
msgstr ""
#: part/templates/part/category.html:19 #: part/templates/part/category.html:19
msgid "All parts" msgid "All parts"
msgstr "" msgstr ""
#: part/templates/part/category.html:23 part/views.py:1976 #: part/templates/part/category.html:24 part/views.py:2043
msgid "Create new part category" msgid "Create new part category"
msgstr "" msgstr ""
#: part/templates/part/category.html:27 #: part/templates/part/category.html:30
msgid "Edit part category" msgid "Edit part category"
msgstr "" msgstr ""
#: part/templates/part/category.html:30 #: part/templates/part/category.html:35
msgid "Delete part category" msgid "Delete part category"
msgstr "" msgstr ""
#: part/templates/part/category.html:39 part/templates/part/category.html:78 #: part/templates/part/category.html:45 part/templates/part/category.html:84
msgid "Category Details" msgid "Category Details"
msgstr "" msgstr ""
#: part/templates/part/category.html:44 #: part/templates/part/category.html:50
msgid "Category Path" msgid "Category Path"
msgstr "" msgstr ""
#: part/templates/part/category.html:49 #: part/templates/part/category.html:55
msgid "Category Description" msgid "Category Description"
msgstr "" msgstr ""
#: part/templates/part/category.html:62 part/templates/part/detail.html:64 #: part/templates/part/category.html:68 part/templates/part/detail.html:64
msgid "Keywords" msgid "Keywords"
msgstr "" msgstr ""
#: part/templates/part/category.html:68 #: part/templates/part/category.html:74
msgid "Subcategories" msgid "Subcategories"
msgstr "" msgstr ""
#: part/templates/part/category.html:73 #: part/templates/part/category.html:79
msgid "Parts (Including subcategories)" msgid "Parts (Including subcategories)"
msgstr "" msgstr ""
#: part/templates/part/category.html:106 #: part/templates/part/category.html:112
msgid "Export Part Data" msgid "Export Part Data"
msgstr "" msgstr ""
#: part/templates/part/category.html:107 part/views.py:491 #: part/templates/part/category.html:114 part/views.py:511
msgid "Create new part" msgid "Create new part"
msgstr "" msgstr ""
#: part/templates/part/category.html:111 #: part/templates/part/category.html:120
msgid "Set category" msgid "Set category"
msgstr "" msgstr ""
#: part/templates/part/category.html:111 #: part/templates/part/category.html:120
msgid "Set Category" msgid "Set Category"
msgstr "" msgstr ""
#: part/templates/part/category.html:113 #: part/templates/part/category.html:123
msgid "Export Data" msgid "Export Data"
msgstr "" msgstr ""
#: part/templates/part/category.html:162 #: part/templates/part/category.html:172
msgid "Create new location" msgid "Create new location"
msgstr "" msgstr ""
#: part/templates/part/category.html:167 part/templates/part/category.html:196 #: part/templates/part/category.html:177 part/templates/part/category.html:207
msgid "New Category" msgid "New Category"
msgstr "" msgstr ""
#: part/templates/part/category.html:168 #: part/templates/part/category.html:178
msgid "Create new category" msgid "Create new category"
msgstr "" msgstr ""
#: part/templates/part/category.html:197 #: part/templates/part/category.html:208
msgid "Create new Part Category" msgid "Create new Part Category"
msgstr "" msgstr ""
#: part/templates/part/category.html:203 stock/views.py:1314 #: part/templates/part/category.html:214 stock/views.py:1314
msgid "Create new Stock Location" msgid "Create new Stock Location"
msgstr "" msgstr ""
@ -2461,7 +2469,7 @@ msgstr ""
msgid "Part Details" msgid "Part Details"
msgstr "" msgstr ""
#: part/templates/part/detail.html:25 part/templates/part/part_base.html:85 #: part/templates/part/detail.html:25 part/templates/part/part_base.html:95
#: templates/js/part.html:112 #: templates/js/part.html:112
msgid "IPN" msgid "IPN"
msgstr "" msgstr ""
@ -2491,7 +2499,7 @@ msgstr ""
msgid "Default Supplier" msgid "Default Supplier"
msgstr "" msgstr ""
#: part/templates/part/detail.html:102 part/templates/part/params.html:22 #: part/templates/part/detail.html:102 part/templates/part/params.html:24
msgid "Units" msgid "Units"
msgstr "" msgstr ""
@ -2616,24 +2624,25 @@ msgstr ""
msgid "Part Parameters" msgid "Part Parameters"
msgstr "" msgstr ""
#: part/templates/part/params.html:13 #: part/templates/part/params.html:14
msgid "Add new parameter" msgid "Add new parameter"
msgstr "" msgstr ""
#: part/templates/part/params.html:13 templates/InvenTree/settings/part.html:12 #: part/templates/part/params.html:14 templates/InvenTree/settings/part.html:12
msgid "New Parameter" msgid "New Parameter"
msgstr "" msgstr ""
#: part/templates/part/params.html:21 stock/models.py:1391 #: part/templates/part/params.html:23 stock/models.py:1391
#: templates/js/stock.html:112 #: templates/js/stock.html:112
msgid "Value" msgid "Value"
msgstr "" msgstr ""
#: part/templates/part/params.html:33 #: part/templates/part/params.html:36
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
#: part/templates/part/params.html:34 part/templates/part/supplier.html:17 #: part/templates/part/params.html:39 part/templates/part/supplier.html:17
#: users/models.py:141
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
@ -2684,39 +2693,43 @@ msgstr ""
msgid "Show pricing information" msgid "Show pricing information"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:70 #: part/templates/part/part_base.html:60
msgid "Part actions" msgid "Count part stock"
msgstr ""
#: part/templates/part/part_base.html:72
msgid "Duplicate part"
msgstr ""
#: part/templates/part/part_base.html:73
msgid "Edit part"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:75 #: part/templates/part/part_base.html:75
msgid "Part actions"
msgstr ""
#: part/templates/part/part_base.html:78
msgid "Duplicate part"
msgstr ""
#: part/templates/part/part_base.html:81
msgid "Edit part"
msgstr ""
#: part/templates/part/part_base.html:84
msgid "Delete part" msgid "Delete part"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:114 templates/js/table_filters.html:65 #: part/templates/part/part_base.html:124 templates/js/table_filters.html:65
msgid "In Stock" msgid "In Stock"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:121 #: part/templates/part/part_base.html:131
msgid "Allocated to Build Orders" msgid "Allocated to Build Orders"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:128 #: part/templates/part/part_base.html:138
msgid "Allocated to Sales Orders" msgid "Allocated to Sales Orders"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:150 #: part/templates/part/part_base.html:160
msgid "Can Build" msgid "Can Build"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:156 #: part/templates/part/part_base.html:166
msgid "Underway" msgid "Underway"
msgstr "" msgstr ""
@ -2736,7 +2749,7 @@ msgstr ""
msgid "Upload new image" msgid "Upload new image"
msgstr "" msgstr ""
#: part/templates/part/sale_prices.html:9 part/templates/part/tabs.html:50 #: part/templates/part/sale_prices.html:9 part/templates/part/tabs.html:53
msgid "Sale Price" msgid "Sale Price"
msgstr "" msgstr ""
@ -2797,11 +2810,11 @@ msgstr ""
msgid "BOM" msgid "BOM"
msgstr "" msgstr ""
#: part/templates/part/tabs.html:34 #: part/templates/part/tabs.html:37
msgid "Used In" msgid "Used In"
msgstr "" msgstr ""
#: part/templates/part/tabs.html:58 stock/templates/stock/item_base.html:282 #: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:282
msgid "Tests" msgid "Tests"
msgstr "" msgstr ""
@ -2829,176 +2842,176 @@ msgstr ""
msgid "New Variant" msgid "New Variant"
msgstr "" msgstr ""
#: part/views.py:76 #: part/views.py:78
msgid "Add part attachment" msgid "Add part attachment"
msgstr "" msgstr ""
#: part/views.py:125 templates/attachment_table.html:30 #: part/views.py:129 templates/attachment_table.html:30
msgid "Edit attachment" msgid "Edit attachment"
msgstr "" msgstr ""
#: part/views.py:129 #: part/views.py:135
msgid "Part attachment updated" msgid "Part attachment updated"
msgstr "" msgstr ""
#: part/views.py:144 #: part/views.py:150
msgid "Delete Part Attachment" msgid "Delete Part Attachment"
msgstr "" msgstr ""
#: part/views.py:150 #: part/views.py:158
msgid "Deleted part attachment" msgid "Deleted part attachment"
msgstr "" msgstr ""
#: part/views.py:159 #: part/views.py:167
msgid "Create Test Template" msgid "Create Test Template"
msgstr "" msgstr ""
#: part/views.py:186 #: part/views.py:196
msgid "Edit Test Template" msgid "Edit Test Template"
msgstr "" msgstr ""
#: part/views.py:200 #: part/views.py:212
msgid "Delete Test Template" msgid "Delete Test Template"
msgstr "" msgstr ""
#: part/views.py:207 #: part/views.py:221
msgid "Set Part Category" msgid "Set Part Category"
msgstr "" msgstr ""
#: part/views.py:255 #: part/views.py:271
#, python-brace-format #, python-brace-format
msgid "Set category for {n} parts" msgid "Set category for {n} parts"
msgstr "" msgstr ""
#: part/views.py:290 #: part/views.py:306
msgid "Create Variant" msgid "Create Variant"
msgstr "" msgstr ""
#: part/views.py:368 #: part/views.py:386
msgid "Duplicate Part" msgid "Duplicate Part"
msgstr "" msgstr ""
#: part/views.py:373 #: part/views.py:393
msgid "Copied part" msgid "Copied part"
msgstr "" msgstr ""
#: part/views.py:496 #: part/views.py:518
msgid "Created new part" msgid "Created new part"
msgstr "" msgstr ""
#: part/views.py:707 #: part/views.py:733
msgid "Part QR Code" msgid "Part QR Code"
msgstr "" msgstr ""
#: part/views.py:724 #: part/views.py:752
msgid "Upload Part Image" msgid "Upload Part Image"
msgstr "" msgstr ""
#: part/views.py:729 part/views.py:764 #: part/views.py:760 part/views.py:797
msgid "Updated part image" msgid "Updated part image"
msgstr "" msgstr ""
#: part/views.py:738 #: part/views.py:769
msgid "Select Part Image" msgid "Select Part Image"
msgstr "" msgstr ""
#: part/views.py:767 #: part/views.py:800
msgid "Part image not found" msgid "Part image not found"
msgstr "" msgstr ""
#: part/views.py:778 #: part/views.py:811
msgid "Edit Part Properties" msgid "Edit Part Properties"
msgstr "" msgstr ""
#: part/views.py:800 #: part/views.py:835
msgid "Validate BOM" msgid "Validate BOM"
msgstr "" msgstr ""
#: part/views.py:963 #: part/views.py:1002
msgid "No BOM file provided" msgid "No BOM file provided"
msgstr "" msgstr ""
#: part/views.py:1313 #: part/views.py:1352
msgid "Enter a valid quantity" msgid "Enter a valid quantity"
msgstr "" msgstr ""
#: part/views.py:1338 part/views.py:1341 #: part/views.py:1377 part/views.py:1380
msgid "Select valid part" msgid "Select valid part"
msgstr "" msgstr ""
#: part/views.py:1347 #: part/views.py:1386
msgid "Duplicate part selected" msgid "Duplicate part selected"
msgstr "" msgstr ""
#: part/views.py:1385 #: part/views.py:1424
msgid "Select a part" msgid "Select a part"
msgstr "" msgstr ""
#: part/views.py:1391 #: part/views.py:1430
msgid "Selected part creates a circular BOM" msgid "Selected part creates a circular BOM"
msgstr "" msgstr ""
#: part/views.py:1395 #: part/views.py:1434
msgid "Specify quantity" msgid "Specify quantity"
msgstr "" msgstr ""
#: part/views.py:1645 #: part/views.py:1690
msgid "Confirm Part Deletion" msgid "Confirm Part Deletion"
msgstr "" msgstr ""
#: part/views.py:1652 #: part/views.py:1699
msgid "Part was deleted" msgid "Part was deleted"
msgstr "" msgstr ""
#: part/views.py:1661 #: part/views.py:1708
msgid "Part Pricing" msgid "Part Pricing"
msgstr "" msgstr ""
#: part/views.py:1783 #: part/views.py:1834
msgid "Create Part Parameter Template" msgid "Create Part Parameter Template"
msgstr "" msgstr ""
#: part/views.py:1791 #: part/views.py:1844
msgid "Edit Part Parameter Template" msgid "Edit Part Parameter Template"
msgstr "" msgstr ""
#: part/views.py:1798 #: part/views.py:1853
msgid "Delete Part Parameter Template" msgid "Delete Part Parameter Template"
msgstr "" msgstr ""
#: part/views.py:1806 #: part/views.py:1863
msgid "Create Part Parameter" msgid "Create Part Parameter"
msgstr "" msgstr ""
#: part/views.py:1856 #: part/views.py:1915
msgid "Edit Part Parameter" msgid "Edit Part Parameter"
msgstr "" msgstr ""
#: part/views.py:1870 #: part/views.py:1931
msgid "Delete Part Parameter" msgid "Delete Part Parameter"
msgstr "" msgstr ""
#: part/views.py:1927 #: part/views.py:1990
msgid "Edit Part Category" msgid "Edit Part Category"
msgstr "" msgstr ""
#: part/views.py:1962 #: part/views.py:2027
msgid "Delete Part Category" msgid "Delete Part Category"
msgstr "" msgstr ""
#: part/views.py:1968 #: part/views.py:2035
msgid "Part category was deleted" msgid "Part category was deleted"
msgstr "" msgstr ""
#: part/views.py:2027 #: part/views.py:2098
msgid "Create BOM item" msgid "Create BOM item"
msgstr "" msgstr ""
#: part/views.py:2093 #: part/views.py:2166
msgid "Edit BOM item" msgid "Edit BOM item"
msgstr "" msgstr ""
#: part/views.py:2141 #: part/views.py:2216
msgid "Confim BOM item deletion" msgid "Confim BOM item deletion"
msgstr "" msgstr ""
@ -3371,15 +3384,15 @@ msgid "Stock adjustment actions"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:98 #: stock/templates/stock/item_base.html:98
#: stock/templates/stock/location.html:38 templates/stock_table.html:15 #: stock/templates/stock/location.html:38 templates/stock_table.html:19
msgid "Count stock" msgid "Count stock"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:99 templates/stock_table.html:13 #: stock/templates/stock/item_base.html:99 templates/stock_table.html:17
msgid "Add stock" msgid "Add stock"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:100 templates/stock_table.html:14 #: stock/templates/stock/item_base.html:100 templates/stock_table.html:18
msgid "Remove stock" msgid "Remove stock"
msgstr "" msgstr ""
@ -3819,6 +3832,14 @@ msgstr ""
msgid "Add Stock Tracking Entry" msgid "Add Stock Tracking Entry"
msgstr "" msgstr ""
#: templates/403.html:5 templates/403.html:11
msgid "Permission Denied"
msgstr ""
#: templates/403.html:14
msgid "You do not have permission to view this page."
msgstr ""
#: templates/InvenTree/bom_invalid.html:7 #: templates/InvenTree/bom_invalid.html:7
msgid "BOM Waiting Validation" msgid "BOM Waiting Validation"
msgstr "" msgstr ""
@ -3827,6 +3848,10 @@ msgstr ""
msgid "Pending Builds" msgid "Pending Builds"
msgstr "" msgstr ""
#: templates/InvenTree/index.html:4
msgid "Index"
msgstr ""
#: templates/InvenTree/latest_parts.html:7 #: templates/InvenTree/latest_parts.html:7
msgid "Latest Parts" msgid "Latest Parts"
msgstr "" msgstr ""
@ -4392,39 +4417,39 @@ msgstr ""
msgid "Purchasable" msgid "Purchasable"
msgstr "" msgstr ""
#: templates/navbar.html:22 #: templates/navbar.html:29
msgid "Buy" msgid "Buy"
msgstr "" msgstr ""
#: templates/navbar.html:30 #: templates/navbar.html:39
msgid "Sell" msgid "Sell"
msgstr "" msgstr ""
#: templates/navbar.html:40 #: templates/navbar.html:50
msgid "Scan Barcode" msgid "Scan Barcode"
msgstr "" msgstr ""
#: templates/navbar.html:49 #: templates/navbar.html:59 users/models.py:27
msgid "Admin" msgid "Admin"
msgstr "" msgstr ""
#: templates/navbar.html:52 #: templates/navbar.html:62
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
#: templates/navbar.html:53 #: templates/navbar.html:63
msgid "Logout" msgid "Logout"
msgstr "" msgstr ""
#: templates/navbar.html:55 #: templates/navbar.html:65
msgid "Login" msgid "Login"
msgstr "" msgstr ""
#: templates/navbar.html:58 #: templates/navbar.html:68
msgid "About InvenTree" msgid "About InvenTree"
msgstr "" msgstr ""
#: templates/navbar.html:59 #: templates/navbar.html:69
msgid "Statistics" msgid "Statistics"
msgstr "" msgstr ""
@ -4436,38 +4461,94 @@ msgstr ""
msgid "Export Stock Information" msgid "Export Stock Information"
msgstr "" msgstr ""
#: templates/stock_table.html:13 #: templates/stock_table.html:17
msgid "Add to selected stock items" msgid "Add to selected stock items"
msgstr "" msgstr ""
#: templates/stock_table.html:14 #: templates/stock_table.html:18
msgid "Remove from selected stock items" msgid "Remove from selected stock items"
msgstr "" msgstr ""
#: templates/stock_table.html:15 #: templates/stock_table.html:19
msgid "Stocktake selected stock items" msgid "Stocktake selected stock items"
msgstr "" msgstr ""
#: templates/stock_table.html:16 #: templates/stock_table.html:20
msgid "Move selected stock items" msgid "Move selected stock items"
msgstr "" msgstr ""
#: templates/stock_table.html:16 #: templates/stock_table.html:20
msgid "Move stock" msgid "Move stock"
msgstr "" msgstr ""
#: templates/stock_table.html:17 #: templates/stock_table.html:21
msgid "Order selected items" msgid "Order selected items"
msgstr "" msgstr ""
#: templates/stock_table.html:17 #: templates/stock_table.html:21
msgid "Order stock" msgid "Order stock"
msgstr "" msgstr ""
#: templates/stock_table.html:18 #: templates/stock_table.html:24
msgid "Delete selected items" msgid "Delete selected items"
msgstr "" msgstr ""
#: templates/stock_table.html:18 #: templates/stock_table.html:24
msgid "Delete Stock" msgid "Delete Stock"
msgstr "" msgstr ""
#: users/admin.py:62
msgid "Users"
msgstr ""
#: users/admin.py:63
msgid "Select which users are assigned to this group"
msgstr ""
#: users/admin.py:124
msgid "Personal info"
msgstr ""
#: users/admin.py:125
msgid "Permissions"
msgstr ""
#: users/admin.py:128
msgid "Important dates"
msgstr ""
#: users/models.py:124
msgid "Permission set"
msgstr ""
#: users/models.py:132
msgid "Group"
msgstr ""
#: users/models.py:135
msgid "View"
msgstr ""
#: users/models.py:135
msgid "Permission to view items"
msgstr ""
#: users/models.py:137
msgid "Create"
msgstr ""
#: users/models.py:137
msgid "Permission to add items"
msgstr ""
#: users/models.py:139
msgid "Update"
msgstr ""
#: users/models.py:139
msgid "Permissions to edit items"
msgstr ""
#: users/models.py:141
msgid "Permission to delete items"
msgstr ""

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-04 14:02+0000\n" "POT-Creation-Date: 2020-10-05 13:20+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -90,7 +90,7 @@ msgstr ""
msgid "User" msgid "User"
msgstr "" msgstr ""
#: InvenTree/models.py:106 part/templates/part/params.html:20 #: InvenTree/models.py:106 part/templates/part/params.html:22
#: templates/js/part.html:81 #: templates/js/part.html:81
msgid "Name" msgid "Name"
msgstr "" msgstr ""
@ -99,19 +99,19 @@ msgstr ""
msgid "Description (optional)" msgid "Description (optional)"
msgstr "" msgstr ""
#: InvenTree/settings.py:342 #: InvenTree/settings.py:343
msgid "English" msgid "English"
msgstr "" msgstr ""
#: InvenTree/settings.py:343 #: InvenTree/settings.py:344
msgid "German" msgid "German"
msgstr "" msgstr ""
#: InvenTree/settings.py:344 #: InvenTree/settings.py:345
msgid "French" msgid "French"
msgstr "" msgstr ""
#: InvenTree/settings.py:345 #: InvenTree/settings.py:346
msgid "Polish" msgid "Polish"
msgstr "" msgstr ""
@ -325,7 +325,7 @@ msgstr ""
msgid "Number of parts to build" msgid "Number of parts to build"
msgstr "" msgstr ""
#: build/models.py:128 part/templates/part/part_base.html:145 #: build/models.py:128 part/templates/part/part_base.html:155
msgid "Build Status" msgid "Build Status"
msgstr "" msgstr ""
@ -344,7 +344,7 @@ msgstr ""
#: build/models.py:155 build/templates/build/detail.html:55 #: build/models.py:155 build/templates/build/detail.html:55
#: company/templates/company/supplier_part_base.html:60 #: company/templates/company/supplier_part_base.html:60
#: company/templates/company/supplier_part_detail.html:24 #: company/templates/company/supplier_part_detail.html:24
#: part/templates/part/detail.html:80 part/templates/part/part_base.html:92 #: part/templates/part/detail.html:80 part/templates/part/part_base.html:102
#: stock/models.py:381 stock/templates/stock/item_base.html:244 #: stock/models.py:381 stock/templates/stock/item_base.html:244
msgid "External Link" msgid "External Link"
msgstr "" msgstr ""
@ -356,7 +356,7 @@ msgstr ""
#: build/models.py:160 build/templates/build/tabs.html:14 company/models.py:310 #: build/models.py:160 build/templates/build/tabs.html:14 company/models.py:310
#: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:15 #: company/templates/company/tabs.html:33 order/templates/order/po_tabs.html:15
#: order/templates/order/purchase_order_detail.html:202 #: order/templates/order/purchase_order_detail.html:202
#: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:67 #: order/templates/order/so_tabs.html:23 part/templates/part/tabs.html:70
#: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453 #: stock/forms.py:306 stock/forms.py:338 stock/forms.py:366 stock/models.py:453
#: stock/models.py:1404 stock/templates/stock/tabs.html:26 #: stock/models.py:1404 stock/templates/stock/tabs.html:26
#: templates/js/barcode.html:391 templates/js/bom.html:223 #: templates/js/barcode.html:391 templates/js/bom.html:223
@ -404,7 +404,7 @@ msgstr ""
#: build/templates/build/allocate.html:17 #: build/templates/build/allocate.html:17
#: company/templates/company/detail_part.html:18 order/views.py:779 #: company/templates/company/detail_part.html:18 order/views.py:779
#: part/templates/part/category.html:112 #: part/templates/part/category.html:122
msgid "Order Parts" msgid "Order Parts"
msgstr "" msgstr ""
@ -420,7 +420,7 @@ msgstr ""
msgid "Unallocate" msgid "Unallocate"
msgstr "" msgstr ""
#: build/templates/build/allocate.html:87 templates/stock_table.html:9 #: build/templates/build/allocate.html:87 templates/stock_table.html:10
msgid "New Stock Item" msgid "New Stock Item"
msgstr "" msgstr ""
@ -548,7 +548,7 @@ msgstr ""
#: build/templates/build/build_base.html:34 #: build/templates/build/build_base.html:34
#: build/templates/build/complete.html:6 #: build/templates/build/complete.html:6
#: stock/templates/stock/item_base.html:223 templates/js/build.html:39 #: stock/templates/stock/item_base.html:223 templates/js/build.html:39
#: templates/navbar.html:20 #: templates/navbar.html:25
msgid "Build" msgid "Build"
msgstr "" msgstr ""
@ -687,7 +687,7 @@ msgstr ""
#: build/templates/build/index.html:6 build/templates/build/index.html:14 #: build/templates/build/index.html:6 build/templates/build/index.html:14
#: order/templates/order/so_builds.html:11 order/templates/order/so_tabs.html:9 #: order/templates/order/so_builds.html:11 order/templates/order/so_tabs.html:9
#: part/templates/part/tabs.html:30 #: part/templates/part/tabs.html:31 users/models.py:30
msgid "Build Orders" msgid "Build Orders"
msgstr "" msgstr ""
@ -709,7 +709,7 @@ msgstr ""
#: build/templates/build/notes.html:33 company/templates/company/notes.html:30 #: build/templates/build/notes.html:33 company/templates/company/notes.html:30
#: order/templates/order/order_notes.html:32 #: order/templates/order/order_notes.html:32
#: order/templates/order/sales_order_notes.html:37 #: order/templates/order/sales_order_notes.html:37
#: part/templates/part/notes.html:32 stock/templates/stock/item_notes.html:33 #: part/templates/part/notes.html:33 stock/templates/stock/item_notes.html:33
msgid "Edit notes" msgid "Edit notes"
msgstr "" msgstr ""
@ -1044,13 +1044,13 @@ msgid "New Supplier Part"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:15 #: company/templates/company/detail_part.html:15
#: part/templates/part/category.html:109 part/templates/part/supplier.html:15 #: part/templates/part/category.html:117 part/templates/part/supplier.html:15
#: templates/stock_table.html:11 #: templates/stock_table.html:14
msgid "Options" msgid "Options"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:18 #: company/templates/company/detail_part.html:18
#: part/templates/part/category.html:112 #: part/templates/part/category.html:122
msgid "Order parts" msgid "Order parts"
msgstr "" msgstr ""
@ -1063,7 +1063,7 @@ msgid "Delete Parts"
msgstr "" msgstr ""
#: company/templates/company/detail_part.html:43 #: company/templates/company/detail_part.html:43
#: part/templates/part/category.html:107 templates/js/stock.html:791 #: part/templates/part/category.html:114 templates/js/stock.html:791
msgid "New Part" msgid "New Part"
msgstr "" msgstr ""
@ -1095,7 +1095,7 @@ msgstr ""
#: company/templates/company/detail_stock.html:35 #: company/templates/company/detail_stock.html:35
#: company/templates/company/supplier_part_stock.html:33 #: company/templates/company/supplier_part_stock.html:33
#: part/templates/part/category.html:106 part/templates/part/category.html:113 #: part/templates/part/category.html:112 part/templates/part/category.html:123
#: part/templates/part/stock.html:51 templates/stock_table.html:6 #: part/templates/part/stock.html:51 templates/stock_table.html:6
msgid "Export" msgid "Export"
msgstr "" msgstr ""
@ -1117,8 +1117,8 @@ msgstr ""
#: company/templates/company/tabs.html:17 #: company/templates/company/tabs.html:17
#: order/templates/order/purchase_orders.html:7 #: order/templates/order/purchase_orders.html:7
#: order/templates/order/purchase_orders.html:12 #: order/templates/order/purchase_orders.html:12
#: part/templates/part/orders.html:9 part/templates/part/tabs.html:45 #: part/templates/part/orders.html:9 part/templates/part/tabs.html:48
#: templates/navbar.html:26 #: templates/navbar.html:33 users/models.py:31
msgid "Purchase Orders" msgid "Purchase Orders"
msgstr "" msgstr ""
@ -1136,8 +1136,8 @@ msgstr ""
#: company/templates/company/tabs.html:22 #: company/templates/company/tabs.html:22
#: order/templates/order/sales_orders.html:7 #: order/templates/order/sales_orders.html:7
#: order/templates/order/sales_orders.html:12 #: order/templates/order/sales_orders.html:12
#: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:53 #: part/templates/part/sales_orders.html:9 part/templates/part/tabs.html:56
#: templates/navbar.html:33 #: templates/navbar.html:42 users/models.py:32
msgid "Sales Orders" msgid "Sales Orders"
msgstr "" msgstr ""
@ -1158,7 +1158,7 @@ msgid "Supplier Part"
msgstr "" msgstr ""
#: company/templates/company/supplier_part_base.html:23 #: company/templates/company/supplier_part_base.html:23
#: part/templates/part/orders.html:14 #: part/templates/part/orders.html:14 part/templates/part/part_base.html:66
msgid "Order part" msgid "Order part"
msgstr "" msgstr ""
@ -1205,7 +1205,7 @@ msgid "Pricing Information"
msgstr "" msgstr ""
#: company/templates/company/supplier_part_pricing.html:15 company/views.py:399 #: company/templates/company/supplier_part_pricing.html:15 company/views.py:399
#: part/templates/part/sale_prices.html:13 part/views.py:2149 #: part/templates/part/sale_prices.html:13 part/views.py:2226
msgid "Add Price Break" msgid "Add Price Break"
msgstr "" msgstr ""
@ -1241,7 +1241,7 @@ msgstr ""
#: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18 #: company/templates/company/tabs.html:12 part/templates/part/tabs.html:18
#: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155 #: stock/templates/stock/location.html:17 templates/InvenTree/search.html:155
#: templates/js/part.html:124 templates/js/part.html:372 #: templates/js/part.html:124 templates/js/part.html:372
#: templates/js/stock.html:452 templates/navbar.html:19 #: templates/js/stock.html:452 templates/navbar.html:22 users/models.py:29
msgid "Stock" msgid "Stock"
msgstr "" msgstr ""
@ -1251,22 +1251,22 @@ msgstr ""
#: company/templates/company/tabs.html:9 #: company/templates/company/tabs.html:9
#: order/templates/order/receive_parts.html:14 part/models.py:294 #: order/templates/order/receive_parts.html:14 part/models.py:294
#: part/templates/part/cat_link.html:7 part/templates/part/category.html:88 #: part/templates/part/cat_link.html:7 part/templates/part/category.html:94
#: part/templates/part/category_tabs.html:6 templates/navbar.html:18 #: part/templates/part/category_tabs.html:6 templates/navbar.html:19
#: templates/stats.html:8 templates/stats.html:17 #: templates/stats.html:8 templates/stats.html:17 users/models.py:28
msgid "Parts" msgid "Parts"
msgstr "" msgstr ""
#: company/views.py:50 part/templates/part/tabs.html:39 #: company/views.py:50 part/templates/part/tabs.html:42
#: templates/navbar.html:24 #: templates/navbar.html:31
msgid "Suppliers" msgid "Suppliers"
msgstr "" msgstr ""
#: company/views.py:57 templates/navbar.html:25 #: company/views.py:57 templates/navbar.html:32
msgid "Manufacturers" msgid "Manufacturers"
msgstr "" msgstr ""
#: company/views.py:64 templates/navbar.html:32 #: company/views.py:64 templates/navbar.html:41
msgid "Customers" msgid "Customers"
msgstr "" msgstr ""
@ -1330,15 +1330,15 @@ msgstr ""
msgid "Delete Supplier Part" msgid "Delete Supplier Part"
msgstr "" msgstr ""
#: company/views.py:404 part/views.py:2153 #: company/views.py:404 part/views.py:2232
msgid "Added new price break" msgid "Added new price break"
msgstr "" msgstr ""
#: company/views.py:441 part/views.py:2198 #: company/views.py:441 part/views.py:2277
msgid "Edit Price Break" msgid "Edit Price Break"
msgstr "" msgstr ""
#: company/views.py:456 part/views.py:2212 #: company/views.py:456 part/views.py:2293
msgid "Delete Price Break" msgid "Delete Price Break"
msgstr "" msgstr ""
@ -1431,7 +1431,7 @@ msgstr ""
msgid "Date order was completed" msgid "Date order was completed"
msgstr "" msgstr ""
#: order/models.py:185 order/models.py:259 part/views.py:1304 #: order/models.py:185 order/models.py:259 part/views.py:1343
#: stock/models.py:241 stock/models.py:805 #: stock/models.py:241 stock/models.py:805
msgid "Quantity must be greater than zero" msgid "Quantity must be greater than zero"
msgstr "" msgstr ""
@ -1600,7 +1600,7 @@ msgid "Purchase Order Attachments"
msgstr "" msgstr ""
#: order/templates/order/po_tabs.html:8 order/templates/order/so_tabs.html:16 #: order/templates/order/po_tabs.html:8 order/templates/order/so_tabs.html:16
#: part/templates/part/tabs.html:64 stock/templates/stock/tabs.html:32 #: part/templates/part/tabs.html:67 stock/templates/stock/tabs.html:32
msgid "Attachments" msgid "Attachments"
msgstr "" msgstr ""
@ -1616,7 +1616,7 @@ msgstr ""
#: order/templates/order/purchase_order_detail.html:38 #: order/templates/order/purchase_order_detail.html:38
#: order/templates/order/purchase_order_detail.html:118 #: order/templates/order/purchase_order_detail.html:118
#: part/templates/part/category.html:161 part/templates/part/category.html:202 #: part/templates/part/category.html:171 part/templates/part/category.html:213
#: templates/js/stock.html:803 #: templates/js/stock.html:803
msgid "New Location" msgid "New Location"
msgstr "" msgstr ""
@ -1658,7 +1658,7 @@ msgid "Select parts to receive against this order"
msgstr "" msgstr ""
#: order/templates/order/receive_parts.html:21 #: order/templates/order/receive_parts.html:21
#: part/templates/part/part_base.html:135 templates/js/part.html:388 #: part/templates/part/part_base.html:145 templates/js/part.html:388
msgid "On Order" msgid "On Order"
msgstr "" msgstr ""
@ -1750,7 +1750,7 @@ msgstr ""
msgid "Add Purchase Order Attachment" msgid "Add Purchase Order Attachment"
msgstr "" msgstr ""
#: order/views.py:102 order/views.py:149 part/views.py:86 stock/views.py:167 #: order/views.py:102 order/views.py:149 part/views.py:90 stock/views.py:167
msgid "Added attachment" msgid "Added attachment"
msgstr "" msgstr ""
@ -1890,12 +1890,12 @@ msgstr ""
msgid "Remove allocation" msgid "Remove allocation"
msgstr "" msgstr ""
#: part/bom.py:138 part/templates/part/category.html:55 #: part/bom.py:138 part/templates/part/category.html:61
#: part/templates/part/detail.html:87 #: part/templates/part/detail.html:87
msgid "Default Location" msgid "Default Location"
msgstr "" msgstr ""
#: part/bom.py:139 part/templates/part/part_base.html:108 #: part/bom.py:139 part/templates/part/part_base.html:118
msgid "Available Stock" msgid "Available Stock"
msgstr "" msgstr ""
@ -2013,7 +2013,7 @@ msgid "Part Category"
msgstr "" msgstr ""
#: part/models.py:76 part/templates/part/category.html:18 #: part/models.py:76 part/templates/part/category.html:18
#: part/templates/part/category.html:83 templates/stats.html:12 #: part/templates/part/category.html:89 templates/stats.html:12
msgid "Part Categories" msgid "Part Categories"
msgstr "" msgstr ""
@ -2226,7 +2226,7 @@ msgstr ""
msgid "BOM line checksum" msgid "BOM line checksum"
msgstr "" msgstr ""
#: part/models.py:1612 part/views.py:1310 part/views.py:1362 #: part/models.py:1612 part/views.py:1349 part/views.py:1401
#: stock/models.py:231 #: stock/models.py:231
msgid "Quantity must be integer value for trackable parts" msgid "Quantity must be integer value for trackable parts"
msgstr "" msgstr ""
@ -2285,23 +2285,23 @@ msgstr ""
msgid "Finish Editing" msgid "Finish Editing"
msgstr "" msgstr ""
#: part/templates/part/bom.html:42 #: part/templates/part/bom.html:43
msgid "Edit BOM" msgid "Edit BOM"
msgstr "" msgstr ""
#: part/templates/part/bom.html:44 #: part/templates/part/bom.html:45
msgid "Validate Bill of Materials" msgid "Validate Bill of Materials"
msgstr "" msgstr ""
#: part/templates/part/bom.html:46 part/views.py:1597 #: part/templates/part/bom.html:48 part/views.py:1640
msgid "Export Bill of Materials" msgid "Export Bill of Materials"
msgstr "" msgstr ""
#: part/templates/part/bom.html:101 #: part/templates/part/bom.html:103
msgid "Delete selected BOM items?" msgid "Delete selected BOM items?"
msgstr "" msgstr ""
#: part/templates/part/bom.html:102 #: part/templates/part/bom.html:104
msgid "All selected BOM items will be deleted" msgid "All selected BOM items will be deleted"
msgstr "" msgstr ""
@ -2373,83 +2373,91 @@ msgstr ""
msgid "Each part must already exist in the database" msgid "Each part must already exist in the database"
msgstr "" msgstr ""
#: part/templates/part/build.html:8
msgid "Part Builds"
msgstr ""
#: part/templates/part/build.html:14
msgid "Start New Build"
msgstr ""
#: part/templates/part/category.html:19 #: part/templates/part/category.html:19
msgid "All parts" msgid "All parts"
msgstr "" msgstr ""
#: part/templates/part/category.html:23 part/views.py:1976 #: part/templates/part/category.html:24 part/views.py:2043
msgid "Create new part category" msgid "Create new part category"
msgstr "" msgstr ""
#: part/templates/part/category.html:27 #: part/templates/part/category.html:30
msgid "Edit part category" msgid "Edit part category"
msgstr "" msgstr ""
#: part/templates/part/category.html:30 #: part/templates/part/category.html:35
msgid "Delete part category" msgid "Delete part category"
msgstr "" msgstr ""
#: part/templates/part/category.html:39 part/templates/part/category.html:78 #: part/templates/part/category.html:45 part/templates/part/category.html:84
msgid "Category Details" msgid "Category Details"
msgstr "" msgstr ""
#: part/templates/part/category.html:44 #: part/templates/part/category.html:50
msgid "Category Path" msgid "Category Path"
msgstr "" msgstr ""
#: part/templates/part/category.html:49 #: part/templates/part/category.html:55
msgid "Category Description" msgid "Category Description"
msgstr "" msgstr ""
#: part/templates/part/category.html:62 part/templates/part/detail.html:64 #: part/templates/part/category.html:68 part/templates/part/detail.html:64
msgid "Keywords" msgid "Keywords"
msgstr "" msgstr ""
#: part/templates/part/category.html:68 #: part/templates/part/category.html:74
msgid "Subcategories" msgid "Subcategories"
msgstr "" msgstr ""
#: part/templates/part/category.html:73 #: part/templates/part/category.html:79
msgid "Parts (Including subcategories)" msgid "Parts (Including subcategories)"
msgstr "" msgstr ""
#: part/templates/part/category.html:106 #: part/templates/part/category.html:112
msgid "Export Part Data" msgid "Export Part Data"
msgstr "" msgstr ""
#: part/templates/part/category.html:107 part/views.py:491 #: part/templates/part/category.html:114 part/views.py:511
msgid "Create new part" msgid "Create new part"
msgstr "" msgstr ""
#: part/templates/part/category.html:111 #: part/templates/part/category.html:120
msgid "Set category" msgid "Set category"
msgstr "" msgstr ""
#: part/templates/part/category.html:111 #: part/templates/part/category.html:120
msgid "Set Category" msgid "Set Category"
msgstr "" msgstr ""
#: part/templates/part/category.html:113 #: part/templates/part/category.html:123
msgid "Export Data" msgid "Export Data"
msgstr "" msgstr ""
#: part/templates/part/category.html:162 #: part/templates/part/category.html:172
msgid "Create new location" msgid "Create new location"
msgstr "" msgstr ""
#: part/templates/part/category.html:167 part/templates/part/category.html:196 #: part/templates/part/category.html:177 part/templates/part/category.html:207
msgid "New Category" msgid "New Category"
msgstr "" msgstr ""
#: part/templates/part/category.html:168 #: part/templates/part/category.html:178
msgid "Create new category" msgid "Create new category"
msgstr "" msgstr ""
#: part/templates/part/category.html:197 #: part/templates/part/category.html:208
msgid "Create new Part Category" msgid "Create new Part Category"
msgstr "" msgstr ""
#: part/templates/part/category.html:203 stock/views.py:1314 #: part/templates/part/category.html:214 stock/views.py:1314
msgid "Create new Stock Location" msgid "Create new Stock Location"
msgstr "" msgstr ""
@ -2461,7 +2469,7 @@ msgstr ""
msgid "Part Details" msgid "Part Details"
msgstr "" msgstr ""
#: part/templates/part/detail.html:25 part/templates/part/part_base.html:85 #: part/templates/part/detail.html:25 part/templates/part/part_base.html:95
#: templates/js/part.html:112 #: templates/js/part.html:112
msgid "IPN" msgid "IPN"
msgstr "" msgstr ""
@ -2491,7 +2499,7 @@ msgstr ""
msgid "Default Supplier" msgid "Default Supplier"
msgstr "" msgstr ""
#: part/templates/part/detail.html:102 part/templates/part/params.html:22 #: part/templates/part/detail.html:102 part/templates/part/params.html:24
msgid "Units" msgid "Units"
msgstr "" msgstr ""
@ -2616,24 +2624,25 @@ msgstr ""
msgid "Part Parameters" msgid "Part Parameters"
msgstr "" msgstr ""
#: part/templates/part/params.html:13 #: part/templates/part/params.html:14
msgid "Add new parameter" msgid "Add new parameter"
msgstr "" msgstr ""
#: part/templates/part/params.html:13 templates/InvenTree/settings/part.html:12 #: part/templates/part/params.html:14 templates/InvenTree/settings/part.html:12
msgid "New Parameter" msgid "New Parameter"
msgstr "" msgstr ""
#: part/templates/part/params.html:21 stock/models.py:1391 #: part/templates/part/params.html:23 stock/models.py:1391
#: templates/js/stock.html:112 #: templates/js/stock.html:112
msgid "Value" msgid "Value"
msgstr "" msgstr ""
#: part/templates/part/params.html:33 #: part/templates/part/params.html:36
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
#: part/templates/part/params.html:34 part/templates/part/supplier.html:17 #: part/templates/part/params.html:39 part/templates/part/supplier.html:17
#: users/models.py:141
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
@ -2684,39 +2693,43 @@ msgstr ""
msgid "Show pricing information" msgid "Show pricing information"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:70 #: part/templates/part/part_base.html:60
msgid "Part actions" msgid "Count part stock"
msgstr ""
#: part/templates/part/part_base.html:72
msgid "Duplicate part"
msgstr ""
#: part/templates/part/part_base.html:73
msgid "Edit part"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:75 #: part/templates/part/part_base.html:75
msgid "Part actions"
msgstr ""
#: part/templates/part/part_base.html:78
msgid "Duplicate part"
msgstr ""
#: part/templates/part/part_base.html:81
msgid "Edit part"
msgstr ""
#: part/templates/part/part_base.html:84
msgid "Delete part" msgid "Delete part"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:114 templates/js/table_filters.html:65 #: part/templates/part/part_base.html:124 templates/js/table_filters.html:65
msgid "In Stock" msgid "In Stock"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:121 #: part/templates/part/part_base.html:131
msgid "Allocated to Build Orders" msgid "Allocated to Build Orders"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:128 #: part/templates/part/part_base.html:138
msgid "Allocated to Sales Orders" msgid "Allocated to Sales Orders"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:150 #: part/templates/part/part_base.html:160
msgid "Can Build" msgid "Can Build"
msgstr "" msgstr ""
#: part/templates/part/part_base.html:156 #: part/templates/part/part_base.html:166
msgid "Underway" msgid "Underway"
msgstr "" msgstr ""
@ -2736,7 +2749,7 @@ msgstr ""
msgid "Upload new image" msgid "Upload new image"
msgstr "" msgstr ""
#: part/templates/part/sale_prices.html:9 part/templates/part/tabs.html:50 #: part/templates/part/sale_prices.html:9 part/templates/part/tabs.html:53
msgid "Sale Price" msgid "Sale Price"
msgstr "" msgstr ""
@ -2797,11 +2810,11 @@ msgstr ""
msgid "BOM" msgid "BOM"
msgstr "" msgstr ""
#: part/templates/part/tabs.html:34 #: part/templates/part/tabs.html:37
msgid "Used In" msgid "Used In"
msgstr "" msgstr ""
#: part/templates/part/tabs.html:58 stock/templates/stock/item_base.html:282 #: part/templates/part/tabs.html:61 stock/templates/stock/item_base.html:282
msgid "Tests" msgid "Tests"
msgstr "" msgstr ""
@ -2829,176 +2842,176 @@ msgstr ""
msgid "New Variant" msgid "New Variant"
msgstr "" msgstr ""
#: part/views.py:76 #: part/views.py:78
msgid "Add part attachment" msgid "Add part attachment"
msgstr "" msgstr ""
#: part/views.py:125 templates/attachment_table.html:30 #: part/views.py:129 templates/attachment_table.html:30
msgid "Edit attachment" msgid "Edit attachment"
msgstr "" msgstr ""
#: part/views.py:129 #: part/views.py:135
msgid "Part attachment updated" msgid "Part attachment updated"
msgstr "" msgstr ""
#: part/views.py:144 #: part/views.py:150
msgid "Delete Part Attachment" msgid "Delete Part Attachment"
msgstr "" msgstr ""
#: part/views.py:150 #: part/views.py:158
msgid "Deleted part attachment" msgid "Deleted part attachment"
msgstr "" msgstr ""
#: part/views.py:159 #: part/views.py:167
msgid "Create Test Template" msgid "Create Test Template"
msgstr "" msgstr ""
#: part/views.py:186 #: part/views.py:196
msgid "Edit Test Template" msgid "Edit Test Template"
msgstr "" msgstr ""
#: part/views.py:200 #: part/views.py:212
msgid "Delete Test Template" msgid "Delete Test Template"
msgstr "" msgstr ""
#: part/views.py:207 #: part/views.py:221
msgid "Set Part Category" msgid "Set Part Category"
msgstr "" msgstr ""
#: part/views.py:255 #: part/views.py:271
#, python-brace-format #, python-brace-format
msgid "Set category for {n} parts" msgid "Set category for {n} parts"
msgstr "" msgstr ""
#: part/views.py:290 #: part/views.py:306
msgid "Create Variant" msgid "Create Variant"
msgstr "" msgstr ""
#: part/views.py:368 #: part/views.py:386
msgid "Duplicate Part" msgid "Duplicate Part"
msgstr "" msgstr ""
#: part/views.py:373 #: part/views.py:393
msgid "Copied part" msgid "Copied part"
msgstr "" msgstr ""
#: part/views.py:496 #: part/views.py:518
msgid "Created new part" msgid "Created new part"
msgstr "" msgstr ""
#: part/views.py:707 #: part/views.py:733
msgid "Part QR Code" msgid "Part QR Code"
msgstr "" msgstr ""
#: part/views.py:724 #: part/views.py:752
msgid "Upload Part Image" msgid "Upload Part Image"
msgstr "" msgstr ""
#: part/views.py:729 part/views.py:764 #: part/views.py:760 part/views.py:797
msgid "Updated part image" msgid "Updated part image"
msgstr "" msgstr ""
#: part/views.py:738 #: part/views.py:769
msgid "Select Part Image" msgid "Select Part Image"
msgstr "" msgstr ""
#: part/views.py:767 #: part/views.py:800
msgid "Part image not found" msgid "Part image not found"
msgstr "" msgstr ""
#: part/views.py:778 #: part/views.py:811
msgid "Edit Part Properties" msgid "Edit Part Properties"
msgstr "" msgstr ""
#: part/views.py:800 #: part/views.py:835
msgid "Validate BOM" msgid "Validate BOM"
msgstr "" msgstr ""
#: part/views.py:963 #: part/views.py:1002
msgid "No BOM file provided" msgid "No BOM file provided"
msgstr "" msgstr ""
#: part/views.py:1313 #: part/views.py:1352
msgid "Enter a valid quantity" msgid "Enter a valid quantity"
msgstr "" msgstr ""
#: part/views.py:1338 part/views.py:1341 #: part/views.py:1377 part/views.py:1380
msgid "Select valid part" msgid "Select valid part"
msgstr "" msgstr ""
#: part/views.py:1347 #: part/views.py:1386
msgid "Duplicate part selected" msgid "Duplicate part selected"
msgstr "" msgstr ""
#: part/views.py:1385 #: part/views.py:1424
msgid "Select a part" msgid "Select a part"
msgstr "" msgstr ""
#: part/views.py:1391 #: part/views.py:1430
msgid "Selected part creates a circular BOM" msgid "Selected part creates a circular BOM"
msgstr "" msgstr ""
#: part/views.py:1395 #: part/views.py:1434
msgid "Specify quantity" msgid "Specify quantity"
msgstr "" msgstr ""
#: part/views.py:1645 #: part/views.py:1690
msgid "Confirm Part Deletion" msgid "Confirm Part Deletion"
msgstr "" msgstr ""
#: part/views.py:1652 #: part/views.py:1699
msgid "Part was deleted" msgid "Part was deleted"
msgstr "" msgstr ""
#: part/views.py:1661 #: part/views.py:1708
msgid "Part Pricing" msgid "Part Pricing"
msgstr "" msgstr ""
#: part/views.py:1783 #: part/views.py:1834
msgid "Create Part Parameter Template" msgid "Create Part Parameter Template"
msgstr "" msgstr ""
#: part/views.py:1791 #: part/views.py:1844
msgid "Edit Part Parameter Template" msgid "Edit Part Parameter Template"
msgstr "" msgstr ""
#: part/views.py:1798 #: part/views.py:1853
msgid "Delete Part Parameter Template" msgid "Delete Part Parameter Template"
msgstr "" msgstr ""
#: part/views.py:1806 #: part/views.py:1863
msgid "Create Part Parameter" msgid "Create Part Parameter"
msgstr "" msgstr ""
#: part/views.py:1856 #: part/views.py:1915
msgid "Edit Part Parameter" msgid "Edit Part Parameter"
msgstr "" msgstr ""
#: part/views.py:1870 #: part/views.py:1931
msgid "Delete Part Parameter" msgid "Delete Part Parameter"
msgstr "" msgstr ""
#: part/views.py:1927 #: part/views.py:1990
msgid "Edit Part Category" msgid "Edit Part Category"
msgstr "" msgstr ""
#: part/views.py:1962 #: part/views.py:2027
msgid "Delete Part Category" msgid "Delete Part Category"
msgstr "" msgstr ""
#: part/views.py:1968 #: part/views.py:2035
msgid "Part category was deleted" msgid "Part category was deleted"
msgstr "" msgstr ""
#: part/views.py:2027 #: part/views.py:2098
msgid "Create BOM item" msgid "Create BOM item"
msgstr "" msgstr ""
#: part/views.py:2093 #: part/views.py:2166
msgid "Edit BOM item" msgid "Edit BOM item"
msgstr "" msgstr ""
#: part/views.py:2141 #: part/views.py:2216
msgid "Confim BOM item deletion" msgid "Confim BOM item deletion"
msgstr "" msgstr ""
@ -3371,15 +3384,15 @@ msgid "Stock adjustment actions"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:98 #: stock/templates/stock/item_base.html:98
#: stock/templates/stock/location.html:38 templates/stock_table.html:15 #: stock/templates/stock/location.html:38 templates/stock_table.html:19
msgid "Count stock" msgid "Count stock"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:99 templates/stock_table.html:13 #: stock/templates/stock/item_base.html:99 templates/stock_table.html:17
msgid "Add stock" msgid "Add stock"
msgstr "" msgstr ""
#: stock/templates/stock/item_base.html:100 templates/stock_table.html:14 #: stock/templates/stock/item_base.html:100 templates/stock_table.html:18
msgid "Remove stock" msgid "Remove stock"
msgstr "" msgstr ""
@ -3819,6 +3832,14 @@ msgstr ""
msgid "Add Stock Tracking Entry" msgid "Add Stock Tracking Entry"
msgstr "" msgstr ""
#: templates/403.html:5 templates/403.html:11
msgid "Permission Denied"
msgstr ""
#: templates/403.html:14
msgid "You do not have permission to view this page."
msgstr ""
#: templates/InvenTree/bom_invalid.html:7 #: templates/InvenTree/bom_invalid.html:7
msgid "BOM Waiting Validation" msgid "BOM Waiting Validation"
msgstr "" msgstr ""
@ -3827,6 +3848,10 @@ msgstr ""
msgid "Pending Builds" msgid "Pending Builds"
msgstr "" msgstr ""
#: templates/InvenTree/index.html:4
msgid "Index"
msgstr ""
#: templates/InvenTree/latest_parts.html:7 #: templates/InvenTree/latest_parts.html:7
msgid "Latest Parts" msgid "Latest Parts"
msgstr "" msgstr ""
@ -4392,39 +4417,39 @@ msgstr ""
msgid "Purchasable" msgid "Purchasable"
msgstr "" msgstr ""
#: templates/navbar.html:22 #: templates/navbar.html:29
msgid "Buy" msgid "Buy"
msgstr "" msgstr ""
#: templates/navbar.html:30 #: templates/navbar.html:39
msgid "Sell" msgid "Sell"
msgstr "" msgstr ""
#: templates/navbar.html:40 #: templates/navbar.html:50
msgid "Scan Barcode" msgid "Scan Barcode"
msgstr "" msgstr ""
#: templates/navbar.html:49 #: templates/navbar.html:59 users/models.py:27
msgid "Admin" msgid "Admin"
msgstr "" msgstr ""
#: templates/navbar.html:52 #: templates/navbar.html:62
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
#: templates/navbar.html:53 #: templates/navbar.html:63
msgid "Logout" msgid "Logout"
msgstr "" msgstr ""
#: templates/navbar.html:55 #: templates/navbar.html:65
msgid "Login" msgid "Login"
msgstr "" msgstr ""
#: templates/navbar.html:58 #: templates/navbar.html:68
msgid "About InvenTree" msgid "About InvenTree"
msgstr "" msgstr ""
#: templates/navbar.html:59 #: templates/navbar.html:69
msgid "Statistics" msgid "Statistics"
msgstr "" msgstr ""
@ -4436,38 +4461,94 @@ msgstr ""
msgid "Export Stock Information" msgid "Export Stock Information"
msgstr "" msgstr ""
#: templates/stock_table.html:13 #: templates/stock_table.html:17
msgid "Add to selected stock items" msgid "Add to selected stock items"
msgstr "" msgstr ""
#: templates/stock_table.html:14 #: templates/stock_table.html:18
msgid "Remove from selected stock items" msgid "Remove from selected stock items"
msgstr "" msgstr ""
#: templates/stock_table.html:15 #: templates/stock_table.html:19
msgid "Stocktake selected stock items" msgid "Stocktake selected stock items"
msgstr "" msgstr ""
#: templates/stock_table.html:16 #: templates/stock_table.html:20
msgid "Move selected stock items" msgid "Move selected stock items"
msgstr "" msgstr ""
#: templates/stock_table.html:16 #: templates/stock_table.html:20
msgid "Move stock" msgid "Move stock"
msgstr "" msgstr ""
#: templates/stock_table.html:17 #: templates/stock_table.html:21
msgid "Order selected items" msgid "Order selected items"
msgstr "" msgstr ""
#: templates/stock_table.html:17 #: templates/stock_table.html:21
msgid "Order stock" msgid "Order stock"
msgstr "" msgstr ""
#: templates/stock_table.html:18 #: templates/stock_table.html:24
msgid "Delete selected items" msgid "Delete selected items"
msgstr "" msgstr ""
#: templates/stock_table.html:18 #: templates/stock_table.html:24
msgid "Delete Stock" msgid "Delete Stock"
msgstr "" msgstr ""
#: users/admin.py:62
msgid "Users"
msgstr ""
#: users/admin.py:63
msgid "Select which users are assigned to this group"
msgstr ""
#: users/admin.py:124
msgid "Personal info"
msgstr ""
#: users/admin.py:125
msgid "Permissions"
msgstr ""
#: users/admin.py:128
msgid "Important dates"
msgstr ""
#: users/models.py:124
msgid "Permission set"
msgstr ""
#: users/models.py:132
msgid "Group"
msgstr ""
#: users/models.py:135
msgid "View"
msgstr ""
#: users/models.py:135
msgid "Permission to view items"
msgstr ""
#: users/models.py:137
msgid "Create"
msgstr ""
#: users/models.py:137
msgid "Permission to add items"
msgstr ""
#: users/models.py:139
msgid "Update"
msgstr ""
#: users/models.py:139
msgid "Permissions to edit items"
msgstr ""
#: users/models.py:141
msgid "Permission to delete items"
msgstr ""

View File

@ -6,7 +6,7 @@ JSON API for the Order app
from __future__ import unicode_literals from __future__ import unicode_literals
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, permissions from rest_framework import generics
from rest_framework import filters from rest_framework import filters
from django.conf.urls import url, include from django.conf.urls import url, include
@ -109,10 +109,6 @@ class POList(generics.ListCreateAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
@ -162,10 +158,6 @@ class PODetail(generics.RetrieveUpdateAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated
]
class POLineItemList(generics.ListCreateAPIView): class POLineItemList(generics.ListCreateAPIView):
""" API endpoint for accessing a list of POLineItem objects """ API endpoint for accessing a list of POLineItem objects
@ -188,10 +180,6 @@ class POLineItemList(generics.ListCreateAPIView):
return self.serializer_class(*args, **kwargs) return self.serializer_class(*args, **kwargs)
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
] ]
@ -208,10 +196,6 @@ class POLineItemDetail(generics.RetrieveUpdateAPIView):
queryset = PurchaseOrderLineItem queryset = PurchaseOrderLineItem
serializer_class = POLineItemSerializer serializer_class = POLineItemSerializer
permission_classes = [
permissions.IsAuthenticated,
]
class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin): class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
""" """
@ -300,10 +284,6 @@ class SOList(generics.ListCreateAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
@ -351,8 +331,6 @@ class SODetail(generics.RetrieveUpdateAPIView):
return queryset return queryset
permission_classes = [permissions.IsAuthenticated]
class SOLineItemList(generics.ListCreateAPIView): class SOLineItemList(generics.ListCreateAPIView):
""" """
@ -398,8 +376,6 @@ class SOLineItemList(generics.ListCreateAPIView):
return queryset return queryset
permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend] filter_backends = [DjangoFilterBackend]
filter_fields = [ filter_fields = [
@ -414,8 +390,6 @@ class SOLineItemDetail(generics.RetrieveUpdateAPIView):
queryset = SalesOrderLineItem.objects.all() queryset = SalesOrderLineItem.objects.all()
serializer_class = SOLineItemSerializer serializer_class = SOLineItemSerializer
permission_classes = [permissions.IsAuthenticated]
class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin): class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
""" """

View File

@ -24,7 +24,7 @@ src="{% static 'img/blank_image.png' %}"
<hr> <hr>
<h4> <h4>
{{ order }} {{ order }}
{% if user.is_staff and perms.order.change_purchaseorder %} {% if user.is_staff and roles.purchase_order.change %}
<a href="{% url 'admin:order_purchaseorder_change' order.pk %}"><span title='{% trans "Admin view" %}' class='fas fa-user-shield'></span></a> <a href="{% url 'admin:order_purchaseorder_change' order.pk %}"><span title='{% trans "Admin view" %}' class='fas fa-user-shield'></span></a>
{% endif %} {% endif %}
</h4> </h4>

View File

@ -34,7 +34,7 @@ src="{% static 'img/blank_image.png' %}"
<hr> <hr>
<h4> <h4>
{{ order }} {{ order }}
{% if user.is_staff and perms.order.change_salesorder %} {% if user.is_staff and roles.sales_order.change %}
<a href="{% url 'admin:order_salesorder_change' order.pk %}"><span title='{% trans "Admin view" %}' class='fas fa-user-shield'></span></a> <a href="{% url 'admin:order_salesorder_change' order.pk %}"><span title='{% trans "Admin view" %}' class='fas fa-user-shield'></span></a>
{% endif %} {% endif %}
</h4> </h4>

View File

@ -44,6 +44,10 @@ class PartCategoryTree(TreeSerializer):
def get_items(self): def get_items(self):
return PartCategory.objects.all().prefetch_related('parts', 'children') return PartCategory.objects.all().prefetch_related('parts', 'children')
permission_classes = [
permissions.IsAuthenticated,
]
class CategoryList(generics.ListCreateAPIView): class CategoryList(generics.ListCreateAPIView):
""" API endpoint for accessing a list of PartCategory objects. """ API endpoint for accessing a list of PartCategory objects.
@ -55,10 +59,6 @@ class CategoryList(generics.ListCreateAPIView):
queryset = PartCategory.objects.all() queryset = PartCategory.objects.all()
serializer_class = part_serializers.CategorySerializer serializer_class = part_serializers.CategorySerializer
permission_classes = [
permissions.IsAuthenticated,
]
def get_queryset(self): def get_queryset(self):
""" """
Custom filtering: Custom filtering:
@ -119,10 +119,6 @@ class PartSalePriceList(generics.ListCreateAPIView):
queryset = PartSellPriceBreak.objects.all() queryset = PartSellPriceBreak.objects.all()
serializer_class = part_serializers.PartSalePriceSerializer serializer_class = part_serializers.PartSalePriceSerializer
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend DjangoFilterBackend
] ]
@ -182,8 +178,6 @@ class PartTestTemplateList(generics.ListCreateAPIView):
return queryset return queryset
permission_classes = [permissions.IsAuthenticated]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.OrderingFilter, filters.OrderingFilter,
@ -221,10 +215,6 @@ class PartThumbsUpdate(generics.RetrieveUpdateAPIView):
queryset = Part.objects.all() queryset = Part.objects.all()
serializer_class = part_serializers.PartThumbSerializerUpdate serializer_class = part_serializers.PartThumbSerializerUpdate
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend DjangoFilterBackend
] ]
@ -246,10 +236,6 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated,
]
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
try: try:
@ -580,10 +566,6 @@ class PartList(generics.ListCreateAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
@ -676,10 +658,6 @@ class PartParameterTemplateList(generics.ListCreateAPIView):
queryset = PartParameterTemplate.objects.all() queryset = PartParameterTemplate.objects.all()
serializer_class = part_serializers.PartParameterTemplateSerializer serializer_class = part_serializers.PartParameterTemplateSerializer
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
filters.OrderingFilter, filters.OrderingFilter,
] ]
@ -699,10 +677,6 @@ class PartParameterList(generics.ListCreateAPIView):
queryset = PartParameter.objects.all() queryset = PartParameter.objects.all()
serializer_class = part_serializers.PartParameterSerializer serializer_class = part_serializers.PartParameterSerializer
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend DjangoFilterBackend
] ]
@ -796,10 +770,6 @@ class BomList(generics.ListCreateAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
@ -816,10 +786,6 @@ class BomDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = BomItem.objects.all() queryset = BomItem.objects.all()
serializer_class = part_serializers.BomItemSerializer serializer_class = part_serializers.BomItemSerializer
permission_classes = [
permissions.IsAuthenticated,
]
class BomItemValidate(generics.UpdateAPIView): class BomItemValidate(generics.UpdateAPIView):
""" API endpoint for validating a BomItem """ """ API endpoint for validating a BomItem """

View File

@ -39,10 +39,12 @@
<button class='btn btn-default action-button' type='button' title='{% trans "New BOM Item" %}' id='bom-item-new'><span class='fas fa-plus-circle'></span></button> <button class='btn btn-default action-button' type='button' title='{% trans "New BOM Item" %}' id='bom-item-new'><span class='fas fa-plus-circle'></span></button>
<button class='btn btn-default action-button' type='button' title='{% trans "Finish Editing" %}' id='editing-finished'><span class='fas fa-check-circle'></span></button> <button class='btn btn-default action-button' type='button' title='{% trans "Finish Editing" %}' id='editing-finished'><span class='fas fa-check-circle'></span></button>
{% elif part.active %} {% elif part.active %}
{% if roles.part.change %}
<button class='btn btn-default action-button' type='button' title='{% trans "Edit BOM" %}' id='edit-bom'><span class='fas fa-edit'></span></button> <button class='btn btn-default action-button' type='button' title='{% trans "Edit BOM" %}' id='edit-bom'><span class='fas fa-edit'></span></button>
{% if part.is_bom_valid == False %} {% if part.is_bom_valid == False %}
<button class='btn btn-default action-button' id='validate-bom' title='{% trans "Validate Bill of Materials" %}' type='button'><span class='fas fa-clipboard-check'></span></button> <button class='btn btn-default action-button' id='validate-bom' title='{% trans "Validate Bill of Materials" %}' type='button'><span class='fas fa-clipboard-check'></span></button>
{% endif %} {% endif %}
{% endif %}
<button title='{% trans "Export Bill of Materials" %}' class='btn btn-default action-button' id='download-bom' type='button'><span class='fas fa-file-download'></span></button> <button title='{% trans "Export Bill of Materials" %}' class='btn btn-default action-button' id='download-bom' type='button'><span class='fas fa-file-download'></span></button>
{% endif %} {% endif %}
</div> </div>

View File

@ -1,15 +1,18 @@
{% extends "part/part_base.html" %} {% extends "part/part_base.html" %}
{% load static %} {% load static %}
{% load i18n %}
{% block details %} {% block details %}
{% include 'part/tabs.html' with tab='build' %} {% include 'part/tabs.html' with tab='build' %}
<h3>Part Builds</h3> <h3>{% trans "Part Builds" %}</h3>
<div id='button-toolbar'> <div id='button-toolbar'>
<div class='button-toolbar container-flui' style='float: right';> <div class='button-toolbar container-flui' style='float: right';>
{% if part.active %} {% if part.active %}
<button class="btn btn-success" id='start-build'>Start New Build</button> {% if roles.build.add %}
<button class="btn btn-success" id='start-build'>{% trans "Start New Build" %}</button>
{% endif %}
{% endif %} {% endif %}
<div class='filter-list' id='filter-list-build'> <div class='filter-list' id='filter-list-build'>
<!-- Empty div for filters --> <!-- Empty div for filters -->

View File

@ -9,7 +9,7 @@
{% if category %} {% if category %}
<h3> <h3>
{{ category.name }} {{ category.name }}
{% if user.is_staff and perms.part.change_partcategory %} {% if user.is_staff and roles.part.change %}
<a href="{% url 'admin:part_partcategory_change' category.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a> <a href="{% url 'admin:part_partcategory_change' category.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
{% endif %} {% endif %}
</h3> </h3>
@ -20,17 +20,23 @@
{% endif %} {% endif %}
<p> <p>
<div class='btn-group action-buttons'> <div class='btn-group action-buttons'>
{% if roles.part.add %}
<button class='btn btn-default' id='cat-create' title='{% trans "Create new part category" %}'> <button class='btn btn-default' id='cat-create' title='{% trans "Create new part category" %}'>
<span class='fas fa-plus-circle icon-green'/> <span class='fas fa-plus-circle icon-green'/>
</button> </button>
{% endif %}
{% if category %} {% if category %}
{% if roles.part.change %}
<button class='btn btn-default' id='cat-edit' title='{% trans "Edit part category" %}'> <button class='btn btn-default' id='cat-edit' title='{% trans "Edit part category" %}'>
<span class='fas fa-edit icon-blue'/> <span class='fas fa-edit icon-blue'/>
</button> </button>
{% endif %}
{% if roles.part.delete %}
<button class='btn btn-default' id='cat-delete' title='{% trans "Delete part category" %}'> <button class='btn btn-default' id='cat-delete' title='{% trans "Delete part category" %}'>
<span class='fas fa-trash-alt icon-red'/> <span class='fas fa-trash-alt icon-red'/>
</button> </button>
{% endif %} {% endif %}
{% endif %}
</div> </div>
</p> </p>
</div> </div>
@ -104,11 +110,15 @@
<div class='button-toolbar container-fluid' style="float: right;"> <div class='button-toolbar container-fluid' style="float: right;">
<div class='btn-group'> <div class='btn-group'>
<button class='btn btn-default' id='part-export' title='{% trans "Export Part Data" %}'>{% trans "Export" %}</button> <button class='btn btn-default' id='part-export' title='{% trans "Export Part Data" %}'>{% trans "Export" %}</button>
{% if roles.part.add %}
<button class='btn btn-success' id='part-create' title='{% trans "Create new part" %}'>{% trans "New Part" %}</button> <button class='btn btn-success' id='part-create' title='{% trans "Create new part" %}'>{% trans "New Part" %}</button>
{% endif %}
<div class='btn-group'> <div class='btn-group'>
<button id='part-options' class='btn btn-primary dropdown-toggle' type='button' data-toggle="dropdown">{% trans "Options" %}<span class='caret'></span></button> <button id='part-options' class='btn btn-primary dropdown-toggle' type='button' data-toggle="dropdown">{% trans "Options" %}<span class='caret'></span></button>
<ul class='dropdown-menu'> <ul class='dropdown-menu'>
{% if roles.part.change %}
<li><a href='#' id='multi-part-category' title='{% trans "Set category" %}'>{% trans "Set Category" %}</a></li> <li><a href='#' id='multi-part-category' title='{% trans "Set category" %}'>{% trans "Set Category" %}</a></li>
{% endif %}
<li><a href='#' id='multi-part-order' title='{% trans "Order parts" %}'>{% trans "Order Parts" %}</a></li> <li><a href='#' id='multi-part-order' title='{% trans "Order parts" %}'>{% trans "Order Parts" %}</a></li>
<li><a href='#' id='multi-part-export' title='{% trans "Export" %}'>{% trans "Export Data" %}</a></li> <li><a href='#' id='multi-part-export' title='{% trans "Export" %}'>{% trans "Export Data" %}</a></li>
</ul> </ul>
@ -180,6 +190,7 @@
location.href = url; location.href = url;
}); });
{% if roles.part.add %}
$("#part-create").click(function() { $("#part-create").click(function() {
launchModalForm( launchModalForm(
"{% url 'part-create' %}", "{% url 'part-create' %}",
@ -207,6 +218,7 @@
} }
); );
}); });
{% endif %}
{% if category %} {% if category %}
$("#cat-edit").click(function () { $("#cat-edit").click(function () {

View File

@ -29,7 +29,9 @@
<h4>{% trans "Part Notes" %}</h4> <h4>{% trans "Part Notes" %}</h4>
</div> </div>
<div class='col-sm-6'> <div class='col-sm-6'>
{% if roles.part.change %}
<button title='{% trans "Edit notes" %}' class='btn btn-default action-button float-right' id='edit-notes'><span class='fas fa-edit'></span></button> <button title='{% trans "Edit notes" %}' class='btn btn-default action-button float-right' id='edit-notes'><span class='fas fa-edit'></span></button>
{% endif %}
</div> </div>
</div> </div>
<hr> <hr>

View File

@ -10,7 +10,9 @@
<div id='button-toolbar'> <div id='button-toolbar'>
<div class='button-toolbar container-fluid' style='float: right;'> <div class='button-toolbar container-fluid' style='float: right;'>
{% if roles.part.add %}
<button title='{% trans "Add new parameter" %}' class='btn btn-success' id='param-create'>{% trans "New Parameter" %}</button> <button title='{% trans "Add new parameter" %}' class='btn btn-success' id='param-create'>{% trans "New Parameter" %}</button>
{% endif %}
</div> </div>
</div> </div>
@ -30,8 +32,12 @@
<td> <td>
{{ param.template.units }} {{ param.template.units }}
<div class='btn-group' style='float: right;'> <div class='btn-group' style='float: right;'>
{% if roles.part.change %}
<button title='{% trans "Edit" %}' class='btn btn-default btn-glyph param-edit' url="{% url 'part-param-edit' param.id %}" type='button'><span class='fas fa-edit'/></button> <button title='{% trans "Edit" %}' class='btn btn-default btn-glyph param-edit' url="{% url 'part-param-edit' param.id %}" type='button'><span class='fas fa-edit'/></button>
{% endif %}
{% if roles.part.delete %}
<button title='{% trans "Delete" %}' class='btn btn-default btn-glyph param-delete' url="{% url 'part-param-delete' param.id %}" type='button'><span class='fas fa-trash-alt icon-red'/></button> <button title='{% trans "Delete" %}' class='btn btn-default btn-glyph param-delete' url="{% url 'part-param-delete' param.id %}" type='button'><span class='fas fa-trash-alt icon-red'/></button>
{% endif %}
</div> </div>
</td> </td>
</tr> </tr>
@ -48,6 +54,7 @@
$('#param-table').inventreeTable({ $('#param-table').inventreeTable({
}); });
{% if roles.part.add %}
$('#param-create').click(function() { $('#param-create').click(function() {
launchModalForm("{% url 'part-param-create' %}?part={{ part.id }}", { launchModalForm("{% url 'part-param-create' %}?part={{ part.id }}", {
reload: true, reload: true,
@ -59,6 +66,7 @@
}], }],
}); });
}); });
{% endif %}
$('.param-edit').click(function() { $('.param-edit').click(function() {
var button = $(this); var button = $(this);

View File

@ -28,7 +28,7 @@
<div class="media-body"> <div class="media-body">
<h3> <h3>
{{ part.full_name }} {{ part.full_name }}
{% if user.is_staff and perms.part.change_part %} {% if user.is_staff and roles.part.change %}
<a href="{% url 'admin:part_part_change' part.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a> <a href="{% url 'admin:part_part_change' part.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
{% endif %} {% endif %}
{% if not part.active %} {% if not part.active %}
@ -56,26 +56,36 @@
<button type='button' class='btn btn-default' id='price-button' title='{% trans "Show pricing information" %}'> <button type='button' class='btn btn-default' id='price-button' title='{% trans "Show pricing information" %}'>
<span id='part-price-icon' class='fas fa-dollar-sign'/> <span id='part-price-icon' class='fas fa-dollar-sign'/>
</button> </button>
<button type='button' class='btn btn-default' id='part-count' title='Count part stock'> {% if roles.stock.change %}
<button type='button' class='btn btn-default' id='part-count' title='{% trans "Count part stock" %}'>
<span class='fas fa-clipboard-list'/> <span class='fas fa-clipboard-list'/>
</button> </button>
{% endif %}
{% if part.purchaseable %} {% if part.purchaseable %}
<button type='button' class='btn btn-default' id='part-order' title='Order part'> {% if roles.purchase_order.add %}
<button type='button' class='btn btn-default' id='part-order' title='{% trans "Order part" %}'>
<span id='part-order-icon' class='fas fa-shopping-cart'/> <span id='part-order-icon' class='fas fa-shopping-cart'/>
</button> </button>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %}
<!-- Part actions --> <!-- Part actions -->
{% if roles.part.add or roles.part.change or roles.part.delete %}
<div class='btn-group'> <div class='btn-group'>
<button id='part-actions' title='{% trans "Part actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'> <span class='fas fa-shapes'></span> <span class='caret'></span></button> <button id='part-actions' title='{% trans "Part actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'> <span class='fas fa-shapes'></span> <span class='caret'></span></button>
<ul class='dropdown-menu'> <ul class='dropdown-menu'>
{% if roles.part.add %}
<li><a href='#' id='part-duplicate'><span class='fas fa-copy'></span> {% trans "Duplicate part" %}</a></li> <li><a href='#' id='part-duplicate'><span class='fas fa-copy'></span> {% trans "Duplicate part" %}</a></li>
{% endif %}
{% if roles.part.change %}
<li><a href='#' id='part-edit'><span class='fas fa-edit icon-blue'></span> {% trans "Edit part" %}</a></li> <li><a href='#' id='part-edit'><span class='fas fa-edit icon-blue'></span> {% trans "Edit part" %}</a></li>
{% if not part.active %} {% endif %}
{% if not part.active and roles.part.delete %}
<li><a href='#' id='part-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete part" %}</a></li> <li><a href='#' id='part-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete part" %}</a></li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
{% endif %}
</div> </div>
<table class='table table-condensed'> <table class='table table-condensed'>
<col width='25'> <col width='25'>
@ -274,6 +284,7 @@
}); });
}); });
{% if roles.part.change %}
$("#part-edit").click(function() { $("#part-edit").click(function() {
launchModalForm( launchModalForm(
"{% url 'part-edit' part.id %}", "{% url 'part-edit' part.id %}",
@ -282,6 +293,7 @@
} }
); );
}); });
{% endif %}
$("#part-order").click(function() { $("#part-order").click(function() {
launchModalForm("{% url 'order-parts' %}", { launchModalForm("{% url 'order-parts' %}", {
@ -292,6 +304,7 @@
}); });
}); });
{% if roles.part.add %}
$("#part-duplicate").click(function() { $("#part-duplicate").click(function() {
launchModalForm( launchModalForm(
"{% url 'part-duplicate' part.id %}", "{% url 'part-duplicate' part.id %}",
@ -300,8 +313,9 @@
} }
); );
}); });
{% endif %}
{% if not part.active %} {% if not part.active and roles.part.delete %}
$("#part-delete").click(function() { $("#part-delete").click(function() {
launchModalForm( launchModalForm(
"{% url 'part-delete' part.id %}", "{% url 'part-delete' part.id %}",

View File

@ -26,14 +26,17 @@
{% if part.assembly %} {% if part.assembly %}
<li{% ifequal tab 'bom' %} class="active"{% endifequal %}> <li{% ifequal tab 'bom' %} class="active"{% endifequal %}>
<a href="{% url 'part-bom' part.id %}">{% trans "BOM" %}<span class="badge{% if part.is_bom_valid == False %} badge-alert{% endif %}">{{ part.bom_count }}</span></a></li> <a href="{% url 'part-bom' part.id %}">{% trans "BOM" %}<span class="badge{% if part.is_bom_valid == False %} badge-alert{% endif %}">{{ part.bom_count }}</span></a></li>
{% if roles.build.view %}
<li{% ifequal tab 'build' %} class="active"{% endifequal %}> <li{% ifequal tab 'build' %} class="active"{% endifequal %}>
<a href="{% url 'part-build' part.id %}">{% trans "Build Orders" %}<span class='badge'>{{ part.builds.count }}</span></a></li> <a href="{% url 'part-build' part.id %}">{% trans "Build Orders" %}<span class='badge'>{{ part.builds.count }}</span></a>
</li>
{% endif %}
{% endif %} {% endif %}
{% if part.component or part.used_in_count > 0 %} {% if part.component or part.used_in_count > 0 %}
<li{% ifequal tab 'used' %} class="active"{% endifequal %}> <li{% ifequal tab 'used' %} class="active"{% endifequal %}>
<a href="{% url 'part-used-in' part.id %}">{% trans "Used In" %} {% if part.used_in_count > 0 %}<span class="badge">{{ part.used_in_count }}</span>{% endif %}</a></li> <a href="{% url 'part-used-in' part.id %}">{% trans "Used In" %} {% if part.used_in_count > 0 %}<span class="badge">{{ part.used_in_count }}</span>{% endif %}</a></li>
{% endif %} {% endif %}
{% if part.purchaseable %} {% if part.purchaseable and roles.purchase_order.view %}
{% if part.is_template == False %} {% if part.is_template == False %}
<li{% ifequal tab 'suppliers' %} class="active"{% endifequal %}> <li{% ifequal tab 'suppliers' %} class="active"{% endifequal %}>
<a href="{% url 'part-suppliers' part.id %}">{% trans "Suppliers" %} <a href="{% url 'part-suppliers' part.id %}">{% trans "Suppliers" %}
@ -45,7 +48,7 @@
<a href="{% url 'part-orders' part.id %}">{% trans "Purchase Orders" %} <span class='badge'>{{ part.purchase_orders|length }}</span></a> <a href="{% url 'part-orders' part.id %}">{% trans "Purchase Orders" %} <span class='badge'>{{ part.purchase_orders|length }}</span></a>
</li> </li>
{% endif %} {% endif %}
{% if part.salable %} {% if part.salable and roles.sales_order.view %}
<li {% if tab == 'sales-prices' %}class='active'{% endif %}> <li {% if tab == 'sales-prices' %}class='active'{% endif %}>
<a href="{% url 'part-sale-prices' part.id %}">{% trans "Sale Price" %}</a> <a href="{% url 'part-sale-prices' part.id %}">{% trans "Sale Price" %}</a>
</li> </li>

View File

@ -3,6 +3,7 @@ from rest_framework import status
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from part.models import Part from part.models import Part
from stock.models import StockItem from stock.models import StockItem
@ -29,7 +30,26 @@ class PartAPITest(APITestCase):
def setUp(self): def setUp(self):
# Create a user for auth # Create a user for auth
User = get_user_model() User = get_user_model()
User.objects.create_user('testuser', 'test@testing.com', 'password') self.user = User.objects.create_user(
username='testuser',
email='test@testing.com',
password='password'
)
# Put the user into a group with the correct permissions
group = Group.objects.create(name='mygroup')
self.user.groups.add(group)
# Give the group *all* the permissions!
for rule in group.rule_sets.all():
rule.can_view = True
rule.can_change = True
rule.can_add = True
rule.can_delete = True
rule.save()
group.save()
self.client.login(username='testuser', password='password') self.client.login(username='testuser', password='password')

View File

@ -3,6 +3,7 @@
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from .models import Part from .models import Part
@ -23,7 +24,24 @@ class PartViewTestCase(TestCase):
# Create a user # Create a user
User = get_user_model() User = get_user_model()
User.objects.create_user('username', 'user@email.com', 'password') self.user = User.objects.create_user(
username='username',
email='user@email.com',
password='password'
)
# Put the user into a group with the correct permissions
group = Group.objects.create(name='mygroup')
self.user.groups.add(group)
# Give the group *all* the permissions!
for rule in group.rule_sets.all():
rule.can_view = True
rule.can_change = True
rule.can_add = True
rule.can_delete = True
rule.save()
self.client.login(username='username', password='password') self.client.login(username='username', password='password')
@ -140,12 +158,14 @@ class PartTests(PartViewTestCase):
""" Tests for Part forms """ """ Tests for Part forms """
def test_part_edit(self): def test_part_edit(self):
response = self.client.get(reverse('part-edit', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest') response = self.client.get(reverse('part-edit', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
keys = response.context.keys() keys = response.context.keys()
data = str(response.content) data = str(response.content)
self.assertEqual(response.status_code, 200)
self.assertIn('part', keys) self.assertIn('part', keys)
self.assertIn('csrf_token', keys) self.assertIn('csrf_token', keys)
@ -189,6 +209,8 @@ class PartAttachmentTests(PartViewTestCase):
response = self.client.get(reverse('part-attachment-create'), {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') response = self.client.get(reverse('part-attachment-create'), {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# TODO - Create a new attachment using this view
def test_invalid_create(self): def test_invalid_create(self):
""" test creation of an attachment for an invalid part """ """ test creation of an attachment for an invalid part """

View File

@ -38,17 +38,21 @@ from .admin import PartResource
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
from InvenTree.views import QRCodeView from InvenTree.views import QRCodeView
from InvenTree.views import InvenTreeRoleMixin
from InvenTree.helpers import DownloadFile, str2bool from InvenTree.helpers import DownloadFile, str2bool
class PartIndex(ListView): class PartIndex(InvenTreeRoleMixin, ListView):
""" View for displaying list of Part objects """ View for displaying list of Part objects
""" """
model = Part model = Part
template_name = 'part/category.html' template_name = 'part/category.html'
context_object_name = 'parts' context_object_name = 'parts'
role_required = 'part.view'
def get_queryset(self): def get_queryset(self):
return Part.objects.all().select_related('category') return Part.objects.all().select_related('category')
@ -76,6 +80,8 @@ class PartAttachmentCreate(AjaxCreateView):
ajax_form_title = _("Add part attachment") ajax_form_title = _("Add part attachment")
ajax_template_name = "modal_form.html" ajax_template_name = "modal_form.html"
role_required = 'part.add'
def post_save(self): def post_save(self):
""" Record the user that uploaded the attachment """ """ Record the user that uploaded the attachment """
self.object.user = self.request.user self.object.user = self.request.user
@ -123,6 +129,8 @@ class PartAttachmentEdit(AjaxUpdateView):
form_class = part_forms.EditPartAttachmentForm form_class = part_forms.EditPartAttachmentForm
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit attachment') ajax_form_title = _('Edit attachment')
role_required = 'part.change'
def get_data(self): def get_data(self):
return { return {
@ -145,6 +153,8 @@ class PartAttachmentDelete(AjaxDeleteView):
ajax_template_name = "attachment_delete.html" ajax_template_name = "attachment_delete.html"
context_object_name = "attachment" context_object_name = "attachment"
role_required = 'part.delete'
def get_data(self): def get_data(self):
return { return {
'danger': _('Deleted part attachment') 'danger': _('Deleted part attachment')
@ -157,6 +167,8 @@ class PartTestTemplateCreate(AjaxCreateView):
model = PartTestTemplate model = PartTestTemplate
form_class = part_forms.EditPartTestTemplateForm form_class = part_forms.EditPartTestTemplateForm
ajax_form_title = _("Create Test Template") ajax_form_title = _("Create Test Template")
role_required = 'part.add'
def get_initial(self): def get_initial(self):
@ -185,6 +197,8 @@ class PartTestTemplateEdit(AjaxUpdateView):
form_class = part_forms.EditPartTestTemplateForm form_class = part_forms.EditPartTestTemplateForm
ajax_form_title = _("Edit Test Template") ajax_form_title = _("Edit Test Template")
role_required = 'part.change'
def get_form(self): def get_form(self):
form = super().get_form() form = super().get_form()
@ -199,6 +213,8 @@ class PartTestTemplateDelete(AjaxDeleteView):
model = PartTestTemplate model = PartTestTemplate
ajax_form_title = _("Delete Test Template") ajax_form_title = _("Delete Test Template")
role_required = 'part.delete'
class PartSetCategory(AjaxUpdateView): class PartSetCategory(AjaxUpdateView):
""" View for settings the part category for multiple parts at once """ """ View for settings the part category for multiple parts at once """
@ -207,6 +223,8 @@ class PartSetCategory(AjaxUpdateView):
ajax_form_title = _('Set Part Category') ajax_form_title = _('Set Part Category')
form_class = part_forms.SetPartCategoryForm form_class = part_forms.SetPartCategoryForm
role_required = 'part.change'
category = None category = None
parts = [] parts = []
@ -290,6 +308,8 @@ class MakePartVariant(AjaxCreateView):
ajax_form_title = _('Create Variant') ajax_form_title = _('Create Variant')
ajax_template_name = 'part/variant_part.html' ajax_template_name = 'part/variant_part.html'
role_required = 'part.add'
def get_part_template(self): def get_part_template(self):
return get_object_or_404(Part, id=self.kwargs['pk']) return get_object_or_404(Part, id=self.kwargs['pk'])
@ -368,6 +388,8 @@ class PartDuplicate(AjaxCreateView):
ajax_form_title = _("Duplicate Part") ajax_form_title = _("Duplicate Part")
ajax_template_name = "part/copy_part.html" ajax_template_name = "part/copy_part.html"
role_required = 'part.add'
def get_data(self): def get_data(self):
return { return {
'success': _('Copied part') 'success': _('Copied part')
@ -491,6 +513,8 @@ class PartCreate(AjaxCreateView):
ajax_form_title = _('Create new part') ajax_form_title = _('Create new part')
ajax_template_name = 'part/create_part.html' ajax_template_name = 'part/create_part.html'
role_required = 'part.add'
def get_data(self): def get_data(self):
return { return {
'success': _("Created new part"), 'success': _("Created new part"),
@ -613,6 +637,8 @@ class PartNotes(UpdateView):
template_name = 'part/notes.html' template_name = 'part/notes.html'
model = Part model = Part
role_required = 'part.change'
fields = ['notes'] fields = ['notes']
def get_success_url(self): def get_success_url(self):
@ -634,7 +660,7 @@ class PartNotes(UpdateView):
return ctx return ctx
class PartDetail(DetailView): class PartDetail(InvenTreeRoleMixin, DetailView):
""" Detail view for Part object """ Detail view for Part object
""" """
@ -642,6 +668,8 @@ class PartDetail(DetailView):
queryset = Part.objects.all().select_related('category') queryset = Part.objects.all().select_related('category')
template_name = 'part/detail.html' template_name = 'part/detail.html'
role_required = 'part.view'
# Add in some extra context information based on query params # Add in some extra context information based on query params
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Provide extra context data to template """ Provide extra context data to template
@ -706,6 +734,8 @@ class PartQRCode(QRCodeView):
ajax_form_title = _("Part QR Code") ajax_form_title = _("Part QR Code")
role_required = 'part.view'
def get_qr_data(self): def get_qr_data(self):
""" Generate QR code data for the Part """ """ Generate QR code data for the Part """
@ -722,8 +752,11 @@ class PartImageUpload(AjaxUpdateView):
model = Part model = Part
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Upload Part Image') ajax_form_title = _('Upload Part Image')
form_class = part_forms.PartImageForm form_class = part_forms.PartImageForm
role_required = 'part.change'
def get_data(self): def get_data(self):
return { return {
'success': _('Updated part image'), 'success': _('Updated part image'),
@ -737,6 +770,8 @@ class PartImageSelect(AjaxUpdateView):
ajax_template_name = 'part/select_image.html' ajax_template_name = 'part/select_image.html'
ajax_form_title = _('Select Part Image') ajax_form_title = _('Select Part Image')
role_required = 'part.change'
fields = [ fields = [
'image', 'image',
] ]
@ -778,6 +813,8 @@ class PartEdit(AjaxUpdateView):
ajax_form_title = _('Edit Part Properties') ajax_form_title = _('Edit Part Properties')
context_object_name = 'part' context_object_name = 'part'
role_required = 'part.change'
def get_form(self): def get_form(self):
""" Create form for Part editing. """ Create form for Part editing.
Overrides default get_form() method to limit the choices Overrides default get_form() method to limit the choices
@ -802,6 +839,8 @@ class BomValidate(AjaxUpdateView):
context_object_name = 'part' context_object_name = 'part'
form_class = part_forms.BomValidateForm form_class = part_forms.BomValidateForm
role_required = 'part.change'
def get_context(self): def get_context(self):
return { return {
'part': self.get_object(), 'part': self.get_object(),
@ -832,7 +871,7 @@ class BomValidate(AjaxUpdateView):
return self.renderJsonResponse(request, form, data, context=self.get_context()) return self.renderJsonResponse(request, form, data, context=self.get_context())
class BomUpload(FormView): class BomUpload(InvenTreeRoleMixin, FormView):
""" View for uploading a BOM file, and handling BOM data importing. """ View for uploading a BOM file, and handling BOM data importing.
The BOM upload process is as follows: The BOM upload process is as follows:
@ -868,6 +907,8 @@ class BomUpload(FormView):
missing_columns = [] missing_columns = []
allowed_parts = [] allowed_parts = []
role_required = ('part.change', 'part.add')
def get_success_url(self): def get_success_url(self):
part = self.get_object() part = self.get_object()
return reverse('upload-bom', kwargs={'pk': part.id}) return reverse('upload-bom', kwargs={'pk': part.id})
@ -1466,6 +1507,8 @@ class BomUpload(FormView):
class PartExport(AjaxView): class PartExport(AjaxView):
""" Export a CSV file containing information on multiple parts """ """ Export a CSV file containing information on multiple parts """
role_required = 'part.view'
def get_parts(self, request): def get_parts(self, request):
""" Extract part list from the POST parameters. """ Extract part list from the POST parameters.
Parts can be supplied as: Parts can be supplied as:
@ -1543,6 +1586,8 @@ class BomDownload(AjaxView):
- File format should be passed as a query param e.g. ?format=csv - File format should be passed as a query param e.g. ?format=csv
""" """
role_required = 'part.view'
model = Part model = Part
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -1596,6 +1641,8 @@ class BomExport(AjaxView):
form_class = part_forms.BomExportForm form_class = part_forms.BomExportForm
ajax_form_title = _("Export Bill of Materials") ajax_form_title = _("Export Bill of Materials")
role_required = 'part.view'
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return self.renderJsonResponse(request, self.form_class()) return self.renderJsonResponse(request, self.form_class())
@ -1645,6 +1692,8 @@ class PartDelete(AjaxDeleteView):
ajax_form_title = _('Confirm Part Deletion') ajax_form_title = _('Confirm Part Deletion')
context_object_name = 'part' context_object_name = 'part'
role_required = 'part.delete'
success_url = '/part/' success_url = '/part/'
def get_data(self): def get_data(self):
@ -1661,6 +1710,8 @@ class PartPricing(AjaxView):
ajax_form_title = _("Part Pricing") ajax_form_title = _("Part Pricing")
form_class = part_forms.PartPriceForm form_class = part_forms.PartPriceForm
role_required = ['sales_order.view', 'part.view']
def get_part(self): def get_part(self):
try: try:
return Part.objects.get(id=self.kwargs['pk']) return Part.objects.get(id=self.kwargs['pk'])
@ -1778,6 +1829,8 @@ class PartPricing(AjaxView):
class PartParameterTemplateCreate(AjaxCreateView): class PartParameterTemplateCreate(AjaxCreateView):
""" View for creating a new PartParameterTemplate """ """ View for creating a new PartParameterTemplate """
role_required = 'part.add'
model = PartParameterTemplate model = PartParameterTemplate
form_class = part_forms.EditPartParameterTemplateForm form_class = part_forms.EditPartParameterTemplateForm
ajax_form_title = _('Create Part Parameter Template') ajax_form_title = _('Create Part Parameter Template')
@ -1786,6 +1839,8 @@ class PartParameterTemplateCreate(AjaxCreateView):
class PartParameterTemplateEdit(AjaxUpdateView): class PartParameterTemplateEdit(AjaxUpdateView):
""" View for editing a PartParameterTemplate """ """ View for editing a PartParameterTemplate """
role_required = 'part.change'
model = PartParameterTemplate model = PartParameterTemplate
form_class = part_forms.EditPartParameterTemplateForm form_class = part_forms.EditPartParameterTemplateForm
ajax_form_title = _('Edit Part Parameter Template') ajax_form_title = _('Edit Part Parameter Template')
@ -1794,6 +1849,8 @@ class PartParameterTemplateEdit(AjaxUpdateView):
class PartParameterTemplateDelete(AjaxDeleteView): class PartParameterTemplateDelete(AjaxDeleteView):
""" View for deleting an existing PartParameterTemplate """ """ View for deleting an existing PartParameterTemplate """
role_required = 'part.delete'
model = PartParameterTemplate model = PartParameterTemplate
ajax_form_title = _("Delete Part Parameter Template") ajax_form_title = _("Delete Part Parameter Template")
@ -1801,6 +1858,8 @@ class PartParameterTemplateDelete(AjaxDeleteView):
class PartParameterCreate(AjaxCreateView): class PartParameterCreate(AjaxCreateView):
""" View for creating a new PartParameter """ """ View for creating a new PartParameter """
role_required = 'part.add'
model = PartParameter model = PartParameter
form_class = part_forms.EditPartParameterForm form_class = part_forms.EditPartParameterForm
ajax_form_title = _('Create Part Parameter') ajax_form_title = _('Create Part Parameter')
@ -1851,6 +1910,8 @@ class PartParameterCreate(AjaxCreateView):
class PartParameterEdit(AjaxUpdateView): class PartParameterEdit(AjaxUpdateView):
""" View for editing a PartParameter """ """ View for editing a PartParameter """
role_required = 'part.change'
model = PartParameter model = PartParameter
form_class = part_forms.EditPartParameterForm form_class = part_forms.EditPartParameterForm
ajax_form_title = _('Edit Part Parameter') ajax_form_title = _('Edit Part Parameter')
@ -1865,12 +1926,14 @@ class PartParameterEdit(AjaxUpdateView):
class PartParameterDelete(AjaxDeleteView): class PartParameterDelete(AjaxDeleteView):
""" View for deleting a PartParameter """ """ View for deleting a PartParameter """
role_required = 'part.delete'
model = PartParameter model = PartParameter
ajax_template_name = 'part/param_delete.html' ajax_template_name = 'part/param_delete.html'
ajax_form_title = _('Delete Part Parameter') ajax_form_title = _('Delete Part Parameter')
class CategoryDetail(DetailView): class CategoryDetail(InvenTreeRoleMixin, DetailView):
""" Detail view for PartCategory """ """ Detail view for PartCategory """
model = PartCategory model = PartCategory
@ -1878,6 +1941,8 @@ class CategoryDetail(DetailView):
queryset = PartCategory.objects.all().prefetch_related('children') queryset = PartCategory.objects.all().prefetch_related('children')
template_name = 'part/category_partlist.html' template_name = 'part/category_partlist.html'
role_required = 'part.view'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CategoryDetail, self).get_context_data(**kwargs).copy() context = super(CategoryDetail, self).get_context_data(**kwargs).copy()
@ -1926,6 +1991,8 @@ class CategoryEdit(AjaxUpdateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit Part Category') ajax_form_title = _('Edit Part Category')
role_required = 'part.change'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CategoryEdit, self).get_context_data(**kwargs).copy() context = super(CategoryEdit, self).get_context_data(**kwargs).copy()
@ -1963,6 +2030,8 @@ class CategoryDelete(AjaxDeleteView):
context_object_name = 'category' context_object_name = 'category'
success_url = '/part/' success_url = '/part/'
role_required = 'part.delete'
def get_data(self): def get_data(self):
return { return {
'danger': _('Part category was deleted'), 'danger': _('Part category was deleted'),
@ -1977,6 +2046,8 @@ class CategoryCreate(AjaxCreateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
form_class = part_forms.EditCategoryForm form_class = part_forms.EditCategoryForm
role_required = 'part.add'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Add extra context data to template. """ Add extra context data to template.
@ -2012,12 +2083,14 @@ class CategoryCreate(AjaxCreateView):
return initials return initials
class BomItemDetail(DetailView): class BomItemDetail(InvenTreeRoleMixin, DetailView):
""" Detail view for BomItem """ """ Detail view for BomItem """
context_object_name = 'item' context_object_name = 'item'
queryset = BomItem.objects.all() queryset = BomItem.objects.all()
template_name = 'part/bom-detail.html' template_name = 'part/bom-detail.html'
role_required = 'part.view'
class BomItemCreate(AjaxCreateView): class BomItemCreate(AjaxCreateView):
""" Create view for making a new BomItem object """ """ Create view for making a new BomItem object """
@ -2026,6 +2099,8 @@ class BomItemCreate(AjaxCreateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Create BOM item') ajax_form_title = _('Create BOM item')
role_required = 'part.add'
def get_form(self): def get_form(self):
""" Override get_form() method to reduce Part selection options. """ Override get_form() method to reduce Part selection options.
@ -2092,6 +2167,8 @@ class BomItemEdit(AjaxUpdateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit BOM item') ajax_form_title = _('Edit BOM item')
role_required = 'part.change'
def get_form(self): def get_form(self):
""" Override get_form() method to filter part selection options """ Override get_form() method to filter part selection options
@ -2140,6 +2217,8 @@ class BomItemDelete(AjaxDeleteView):
context_object_name = 'item' context_object_name = 'item'
ajax_form_title = _('Confim BOM item deletion') ajax_form_title = _('Confim BOM item deletion')
role_required = 'part.delete'
class PartSalePriceBreakCreate(AjaxCreateView): class PartSalePriceBreakCreate(AjaxCreateView):
""" View for creating a sale price break for a part """ """ View for creating a sale price break for a part """
@ -2147,6 +2226,8 @@ class PartSalePriceBreakCreate(AjaxCreateView):
model = PartSellPriceBreak model = PartSellPriceBreak
form_class = part_forms.EditPartSalePriceBreakForm form_class = part_forms.EditPartSalePriceBreakForm
ajax_form_title = _('Add Price Break') ajax_form_title = _('Add Price Break')
role_required = 'part.add'
def get_data(self): def get_data(self):
return { return {
@ -2197,6 +2278,8 @@ class PartSalePriceBreakEdit(AjaxUpdateView):
form_class = part_forms.EditPartSalePriceBreakForm form_class = part_forms.EditPartSalePriceBreakForm
ajax_form_title = _('Edit Price Break') ajax_form_title = _('Edit Price Break')
role_required = 'part.change'
def get_form(self): def get_form(self):
form = super().get_form() form = super().get_form()
@ -2211,3 +2294,5 @@ class PartSalePriceBreakDelete(AjaxDeleteView):
model = PartSellPriceBreak model = PartSellPriceBreak
ajax_form_title = _("Delete Price Break") ajax_form_title = _("Delete Price Break")
ajax_template_name = "modal_delete_form.html" ajax_template_name = "modal_delete_form.html"
role_required = 'part.delete'

View File

@ -52,6 +52,10 @@ class StockCategoryTree(TreeSerializer):
def get_items(self): def get_items(self):
return StockLocation.objects.all().prefetch_related('stock_items', 'children') return StockLocation.objects.all().prefetch_related('stock_items', 'children')
permission_classes = [
permissions.IsAuthenticated,
]
class StockDetail(generics.RetrieveUpdateDestroyAPIView): class StockDetail(generics.RetrieveUpdateDestroyAPIView):
""" API detail endpoint for Stock object """ API detail endpoint for Stock object
@ -68,7 +72,6 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = StockItem.objects.all() queryset = StockItem.objects.all()
serializer_class = StockItemSerializer serializer_class = StockItemSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self, *args, **kwargs): def get_queryset(self, *args, **kwargs):
@ -289,10 +292,6 @@ class StockLocationList(generics.ListCreateAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
@ -695,10 +694,6 @@ class StockList(generics.ListCreateAPIView):
return queryset return queryset
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
@ -744,10 +739,6 @@ class StockItemTestResultList(generics.ListCreateAPIView):
queryset = StockItemTestResult.objects.all() queryset = StockItemTestResult.objects.all()
serializer_class = StockItemTestResultSerializer serializer_class = StockItemTestResultSerializer
permission_classes = [
permissions.IsAuthenticated,
]
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
@ -799,7 +790,6 @@ class StockTrackingList(generics.ListCreateAPIView):
queryset = StockItemTracking.objects.all() queryset = StockItemTracking.objects.all()
serializer_class = StockTrackingSerializer serializer_class = StockTrackingSerializer
permission_classes = [permissions.IsAuthenticated]
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
try: try:
@ -871,7 +861,6 @@ class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = StockLocation.objects.all() queryset = StockLocation.objects.all()
serializer_class = LocationSerializer serializer_class = LocationSerializer
permission_classes = (permissions.IsAuthenticated,)
stock_endpoints = [ stock_endpoints = [

View File

@ -65,7 +65,7 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
{% else %} {% else %}
<a href='{% url "part-detail" item.part.pk %}'>{{ item.part.full_name }}</a> &times {% decimal item.quantity %} <a href='{% url "part-detail" item.part.pk %}'>{{ item.part.full_name }}</a> &times {% decimal item.quantity %}
{% endif %} {% endif %}
{% if user.is_staff and perms.stock.change_stockitem %} {% if user.is_staff and roles.stock.change %}
<a href="{% url 'admin:stock_stockitem_change' item.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a> <a href="{% url 'admin:stock_stockitem_change' item.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
{% endif %} {% endif %}
</h4> </h4>

View File

@ -8,7 +8,7 @@
{% if location %} {% if location %}
<h3> <h3>
{{ location.name }} {{ location.name }}
{% if user.is_staff and perms.stock.change_stocklocation %} {% if user.is_staff and roles.stock.change %}
<a href="{% url 'admin:stock_stocklocation_change' location.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a> <a href="{% url 'admin:stock_stocklocation_change' location.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
{% endif %} {% endif %}
</h3> </h3>

View File

@ -3,6 +3,8 @@ from rest_framework import status
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from InvenTree.helpers import addUserPermissions
from .models import StockLocation from .models import StockLocation
@ -22,6 +24,20 @@ class StockAPITestCase(APITestCase):
# Create a user for auth # Create a user for auth
User = get_user_model() User = get_user_model()
self.user = User.objects.create_user('testuser', 'test@testing.com', 'password') self.user = User.objects.create_user('testuser', 'test@testing.com', 'password')
# Add the necessary permissions to the user
perms = [
'view_stockitemtestresult',
'change_stockitemtestresult',
'add_stockitemtestresult',
'add_stocklocation',
'change_stocklocation',
'add_stockitem',
'change_stockitem',
]
addUserPermissions(self.user, perms)
self.client.login(username='testuser', password='password') self.client.login(username='testuser', password='password')
def doPost(self, url, data={}): def doPost(self, url, data={}):

View File

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% load i18n %}
{% block page_title %}
InvenTree | {% trans "Permission Denied" %}
{% endblock %}
{% block content %}
<div class='container-fluid'>
<h3>{% trans "Permission Denied" %}</h3>
<div class='alert alert-danger alert-block'>
{% trans "You do not have permission to view this page." %}
</div>
</div>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %}
{% block page_title %} {% block page_title %}
InvenTree | Index InvenTree | {% trans "Index" %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -9,24 +9,24 @@ InvenTree | Index
<hr> <hr>
<div class='col-sm-6'> <div class='col-sm-6'>
{% if perms.part.view_part %} {% if roles.part.view %}
{% include "InvenTree/latest_parts.html" with collapse_id="latest_parts" %} {% include "InvenTree/latest_parts.html" with collapse_id="latest_parts" %}
{% include "InvenTree/bom_invalid.html" with collapse_id="bom_invalid" %} {% include "InvenTree/bom_invalid.html" with collapse_id="bom_invalid" %}
{% include "InvenTree/starred_parts.html" with collapse_id="starred" %} {% include "InvenTree/starred_parts.html" with collapse_id="starred" %}
{% endif %} {% endif %}
{% if perms.build.view_build %} {% if roles.build.view %}
{% include "InvenTree/build_pending.html" with collapse_id="build_pending" %} {% include "InvenTree/build_pending.html" with collapse_id="build_pending" %}
{% endif %} {% endif %}
</div> </div>
<div class='col-sm-6'> <div class='col-sm-6'>
{% if perms.stock.view_stockitem %} {% if roles.stock.view %}
{% include "InvenTree/low_stock.html" with collapse_id="order" %} {% include "InvenTree/low_stock.html" with collapse_id="order" %}
{% include "InvenTree/required_stock_build.html" with collapse_id="stock_to_build" %} {% include "InvenTree/required_stock_build.html" with collapse_id="stock_to_build" %}
{% endif %} {% endif %}
{% if perms.order.view_purchaseorder %} {% if roles.purchase_order.view %}
{% include "InvenTree/po_outstanding.html" with collapse_id="po_outstanding" %} {% include "InvenTree/po_outstanding.html" with collapse_id="po_outstanding" %}
{% endif %} {% endif %}
{% if perms.order.view_salesorder %} {% if roles.sales_order.view %}
{% include "InvenTree/so_outstanding.html" with collapse_id="so_outstanding" %} {% include "InvenTree/so_outstanding.html" with collapse_id="so_outstanding" %}
{% endif %} {% endif %}
</div> </div>

View File

@ -15,16 +15,16 @@
</div> </div>
<div class="navbar-collapse collapse"> <div class="navbar-collapse collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
{% if perms.part.view_part or perms.part.view_partcategory %} {% if roles.part.view %}
<li><a href="{% url 'part-index' %}"><span class='fas fa-shapes icon-header'></span>{% trans "Parts" %}</a></li> <li><a href="{% url 'part-index' %}"><span class='fas fa-shapes icon-header'></span>{% trans "Parts" %}</a></li>
{% endif %} {% endif %}
{% if perms.stock.view_stockitem or perms.part.view_stocklocation %} {% if roles.stock.view %}
<li><a href="{% url 'stock-index' %}"><span class='fas fa-boxes icon-header'></span>{% trans "Stock" %}</a></li> <li><a href="{% url 'stock-index' %}"><span class='fas fa-boxes icon-header'></span>{% trans "Stock" %}</a></li>
{% endif %} {% endif %}
{% if perms.build.view_build %} {% if roles.build.view %}
<li><a href="{% url 'build-index' %}"><span class='fas fa-tools icon-header'></span>{% trans "Build" %}</a></li> <li><a href="{% url 'build-index' %}"><span class='fas fa-tools icon-header'></span>{% trans "Build" %}</a></li>
{% endif %} {% endif %}
{% if perms.order.view_purchaseorder %} {% if roles.purchase_order.view %}
<li class='nav navbar-nav'> <li class='nav navbar-nav'>
<a class='dropdown-toggle' data-toggle='dropdown' href='#'><span class='fas fa-shopping-cart icon-header'></span>{% trans "Buy" %}</a> <a class='dropdown-toggle' data-toggle='dropdown' href='#'><span class='fas fa-shopping-cart icon-header'></span>{% trans "Buy" %}</a>
<ul class='dropdown-menu'> <ul class='dropdown-menu'>
@ -34,7 +34,7 @@
</ul> </ul>
</li> </li>
{% endif %} {% endif %}
{% if perms.order.view_salesorder %} {% if roles.sales_order.view %}
<li class='nav navbar-nav'> <li class='nav navbar-nav'>
<a class='dropdown-toggle' data-toggle='dropdown' href='#'><span class='fas fa-truck icon-header'></span>{% trans "Sell" %}</a> <a class='dropdown-toggle' data-toggle='dropdown' href='#'><span class='fas fa-truck icon-header'></span>{% trans "Sell" %}</a>
<ul class='dropdown-menu'> <ul class='dropdown-menu'>

View File

@ -1,3 +1,3 @@
<div> <div>
<input fieldname='{{ field }}' class='slidey' type="checkbox" data-offstyle='warning' data-onstyle="success" data-size='small' data-toggle="toggle" {% if disabled %}disabled {% endif %}{% if state %}checked=""{% endif %} autocomplete="off"> <input fieldname='{{ field }}' class='slidey' type="checkbox" data-offstyle='warning' data-onstyle="success" data-size='small' data-toggle="toggle" {% if disabled or not roles.part.change %}disabled {% endif %}{% if state %}checked=""{% endif %} autocomplete="off">
</div> </div>

View File

@ -6,19 +6,27 @@
<button class='btn btn-default' id='stock-export' title='{% trans "Export Stock Information" %}'>{% trans "Export" %}</button> <button class='btn btn-default' id='stock-export' title='{% trans "Export Stock Information" %}'>{% trans "Export" %}</button>
{% if read_only %} {% if read_only %}
{% else %} {% else %}
{% if roles.stock.add %}
<button class="btn btn-success" id='item-create'>{% trans "New Stock Item" %}</button> <button class="btn btn-success" id='item-create'>{% trans "New Stock Item" %}</button>
{% endif %}
{% if roles.stock.change or roles.stock.delete %}
<div class="btn-group"> <div class="btn-group">
<button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button> <button id='stock-options' class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}<span class="caret"></span></button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% if roles.stock.change %}
<li><a href="#" id='multi-item-add' title='{% trans "Add to selected stock items" %}'>{% trans "Add stock" %}</a></li> <li><a href="#" id='multi-item-add' title='{% trans "Add to selected stock items" %}'>{% trans "Add stock" %}</a></li>
<li><a href="#" id='multi-item-remove' title='{% trans "Remove from selected stock items" %}'>{% trans "Remove stock" %}</a></li> <li><a href="#" id='multi-item-remove' title='{% trans "Remove from selected stock items" %}'>{% trans "Remove stock" %}</a></li>
<li><a href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'>{% trans "Count stock" %}</a></li> <li><a href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'>{% trans "Count stock" %}</a></li>
<li><a href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'>{% trans "Move stock" %}</a></li> <li><a href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'>{% trans "Move stock" %}</a></li>
<li><a href='#' id='multi-item-order' title='{% trans "Order selected items" %}'>{% trans "Order stock" %}</a></li> <li><a href='#' id='multi-item-order' title='{% trans "Order selected items" %}'>{% trans "Order stock" %}</a></li>
{% endif %}
{% if roles.stock.delete %}
<li><a href='#' id='multi-item-delete' title='{% trans "Delete selected items" %}'>{% trans "Delete Stock" %}</a></li> <li><a href='#' id='multi-item-delete' title='{% trans "Delete selected items" %}'>{% trans "Delete Stock" %}</a></li>
{% endif %}
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
{% endif %}
</div> </div>
<div class='filter-list' id='filter-list-stock'> <div class='filter-list' id='filter-list-stock'>
<!-- An empty div in which the filter list will be constructed --> <!-- An empty div in which the filter list will be constructed -->

View File

@ -0,0 +1,23 @@
# Generated by Django 3.0.7 on 2020-10-05 22:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_auto_20201004_0158'),
]
operations = [
migrations.AlterField(
model_name='ruleset',
name='can_add',
field=models.BooleanField(default=False, help_text='Permission to add items', verbose_name='Add'),
),
migrations.AlterField(
model_name='ruleset',
name='can_change',
field=models.BooleanField(default=False, help_text='Permissions to edit items', verbose_name='Change'),
),
]

View File

@ -134,9 +134,9 @@ class RuleSet(models.Model):
can_view = models.BooleanField(verbose_name=_('View'), default=True, help_text=_('Permission to view items')) can_view = models.BooleanField(verbose_name=_('View'), default=True, help_text=_('Permission to view items'))
can_add = models.BooleanField(verbose_name=_('Create'), default=False, help_text=_('Permission to add items')) can_add = models.BooleanField(verbose_name=_('Add'), default=False, help_text=_('Permission to add items'))
can_change = models.BooleanField(verbose_name=_('Update'), default=False, help_text=_('Permissions to edit items')) can_change = models.BooleanField(verbose_name=_('Change'), default=False, help_text=_('Permissions to edit items'))
can_delete = models.BooleanField(verbose_name=_('Delete'), default=False, help_text=_('Permission to delete items')) can_delete = models.BooleanField(verbose_name=_('Delete'), default=False, help_text=_('Permission to delete items'))
@ -178,6 +178,10 @@ class RuleSet(models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
if self.group:
# Update the group too!
self.group.save()
def get_models(self): def get_models(self):
""" """
Return the database tables / models that this ruleset covers. Return the database tables / models that this ruleset covers.
@ -334,9 +338,37 @@ def create_missing_rule_sets(sender, instance, **kwargs):
then we can now use these RuleSet values to update the then we can now use these RuleSet values to update the
group permissions. group permissions.
""" """
created = kwargs.get('created', False)
# To trigger the group permissions update: update_fields should not be None
update_fields = kwargs.get('update_fields', None)
if created or update_fields: update_group_roles(instance)
update_group_roles(instance)
def check_user_role(user, role, permission):
"""
Check if a user has a particular role:permission combination.
If the user is a superuser, this will return True
"""
if user.is_superuser:
return True
for group in user.groups.all():
for rule in group.rule_sets.all():
if rule.name == role:
if permission == 'add' and rule.can_add:
return True
if permission == 'change' and rule.can_change:
return True
if permission == 'view' and rule.can_view:
return True
if permission == 'delete' and rule.can_delete:
return True
# No matching permissions found
return False