diff --git a/InvenTree/script/translation_stats.py b/InvenTree/script/translation_stats.py index 36032ed1e0..85740053f8 100644 --- a/InvenTree/script/translation_stats.py +++ b/InvenTree/script/translation_stats.py @@ -6,10 +6,7 @@ import sys def calculate_coverage(filename): - """ - Calculate translation coverage for a .po file - """ - + """Calculate translation coverage for a .po file""" with open(filename, 'r') as f: lines = f.readlines() diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 56b316eb50..a03de387ce 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -212,9 +212,7 @@ class StockLocationList(generics.ListCreateAPIView): serializer_class = StockSerializers.LocationSerializer def filter_queryset(self, queryset): - """Custom filtering: - - Allow filtering by "null" parent to retrieve top-level stock locations - """ + """Custom filtering: - Allow filtering by "null" parent to retrieve top-level stock locations""" queryset = super().filter_queryset(queryset) params = self.request.query_params @@ -397,7 +395,9 @@ class StockFilter(rest_filters.FilterSet): tracked = rest_filters.BooleanFilter(label='Tracked', method='filter_tracked') def filter_tracked(self, queryset, name, value): - """Filter by whether this stock item is *tracked*, meaning either: + """Filter by whether this stock item is *tracked*. + + Meaning either: - It has a serial number - It has a batch code """ @@ -616,8 +616,7 @@ class StockList(APIDownloadMixin, generics.ListCreateAPIView): return DownloadFile(filedata, filename) def list(self, request, *args, **kwargs): - """Override the 'list' method, as the StockLocation objects - are very expensive to serialize. + """Override the 'list' method, as the StockLocation objects are very expensive to serialize. So, we fetch and serialize the required StockLocation objects only as required. """ @@ -731,7 +730,6 @@ class StockList(APIDownloadMixin, generics.ListCreateAPIView): def filter_queryset(self, queryset): """Custom filtering for the StockItem queryset""" - params = self.request.query_params queryset = super().filter_queryset(queryset) @@ -1155,7 +1153,6 @@ class StockItemTestResultList(generics.ListCreateAPIView): Also, check if an attachment was uploaded alongside the test result, and save it to the database if it were. """ - # Capture the user information test_result = serializer.save() test_result.user = self.request.user @@ -1267,7 +1264,6 @@ class StockTrackingList(generics.ListAPIView): Here we override the default 'create' implementation, to save the user information associated with the request object. """ - serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 2eb74a742f..fee23e7136 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -46,6 +46,7 @@ class StockLocation(MetadataMixin, InvenTreeTree): def delete(self, *args, **kwargs): """Custom model deletion routine, which updates any child locations or items. + This must be handled within a transaction.atomic(), otherwise the tree structure is damaged """ with transaction.atomic(): @@ -161,7 +162,8 @@ class StockLocation(MetadataMixin, InvenTreeTree): def item_count(self): """Simply returns the number of stock items in this location. - Required for tree view serializer.""" + Required for tree view serializer. + """ return self.stock_item_count() @@ -248,10 +250,7 @@ class StockItem(MetadataMixin, MPTTModel): return reverse('api-stock-list') def api_instance_filters(self): - """ - Custom API instance filters - """ - + """Custom API instance filters""" return { 'parent': { 'exclude_tree': self.pk, @@ -538,6 +537,7 @@ class StockItem(MetadataMixin, MPTTModel): def format_barcode(self, **kwargs): """Return a JSON string for formatting a barcode for this StockItem. + Can be used to perform lookup of a stockitem using barcode Contains the following data: @@ -1025,7 +1025,7 @@ class StockItem(MetadataMixin, MPTTModel): def installStockItem(self, other_item, quantity, user, notes): """Install another stock item into this stock item. - Args + Args: other_item: The stock item to install into this stock item quantity: The quantity of stock to install user: The user performing the operation @@ -1117,7 +1117,8 @@ class StockItem(MetadataMixin, MPTTModel): def child_count(self): """Return the number of 'child' items associated with this StockItem. - A child item is one which has been split from this one.""" + A child item is one which has been split from this one. + """ return self.children.count() @property @@ -1581,6 +1582,7 @@ class StockItem(MetadataMixin, MPTTModel): @transaction.atomic def stocktake(self, count, user, notes=''): """Perform item stocktake. + When the quantity of an item is counted, record the date of stocktake """ @@ -1611,6 +1613,7 @@ class StockItem(MetadataMixin, MPTTModel): @transaction.atomic def add_stock(self, quantity, user, notes=''): """Add items to stock + This function can be called by initiating a ProjectRun, or by manually adding the items to the stock location """ @@ -1769,7 +1772,7 @@ class StockItem(MetadataMixin, MPTTModel): def requiredTestStatus(self): """Return the status of the tests required for this StockItem. - return: + Return: A dict containing the following items: - total: Number of required tests - passed: Number of tests that have passed diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index 7c22ff31c7..1bd7009760 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -325,9 +325,7 @@ class StockItemTest(StockAPITestCase): StockLocation.objects.create(name='C', description='location c', parent=top) def test_create_default_location(self): - """Test the default location functionality, - if a 'location' is not specified in the creation request. - """ + """Test the default location functionality, if a 'location' is not specified in the creation request.""" # The part 'R_4K7_0603' (pk=4) has a default location specified response = self.client.post( diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 8d3a59e8bf..b739c4b624 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -212,7 +212,6 @@ class StockItemConvert(AjaxUpdateView): def get_form(self): """Filter the available parts.""" - form = super().get_form() item = self.get_object() diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py index 0e533bb164..902da70fd9 100644 --- a/InvenTree/users/admin.py +++ b/InvenTree/users/admin.py @@ -30,8 +30,7 @@ class RuleSetInline(admin.TabularInline): class InvenTreeGroupAdminForm(forms.ModelForm): - """ - Custom admin form for the Group model. + """Custom admin form for the Group model. Adds the ability for editing user membership directly in the group admin page. """ @@ -86,8 +85,7 @@ class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover 'stock_item', 'build', 'purchase_order', 'sales_order') def get_rule_set(self, obj, rule_set_type): - ''' Return list of permissions for the given ruleset ''' - + """Return list of permissions for the given ruleset""" # Get all rulesets associated to object rule_sets = RuleSet.objects.filter(group=obj.pk) @@ -156,12 +154,10 @@ class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover filter_horizontal = ['permissions'] def save_model(self, request, obj, form, change): - """ - This method serves two purposes: + """This method serves two purposes: - show warning message whenever the group users belong to multiple groups - skip saving of the group instance model as inlines needs to be saved before. """ - # Get form cleaned data users = form.cleaned_data['users'] @@ -189,8 +185,7 @@ class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover class InvenTreeUserAdmin(UserAdmin): - """ - Custom admin page for the User model. + """Custom admin page for the User model. Hides the "permissions" view as this is now handled entirely by groups and RuleSets. diff --git a/InvenTree/users/api.py b/InvenTree/users/api.py index be8d83b58b..64edc6dc64 100644 --- a/InvenTree/users/api.py +++ b/InvenTree/users/api.py @@ -20,8 +20,7 @@ class OwnerList(generics.ListAPIView): serializer_class = OwnerSerializer def filter_queryset(self, queryset): - """ - Implement text search for the "owner" model. + """Implement text search for the "owner" model. Note that an "owner" can be either a group, or a user, so we cannot do a direct text search. @@ -59,8 +58,7 @@ class OwnerDetail(generics.RetrieveAPIView): class RoleDetails(APIView): - """ - API endpoint which lists the available role permissions + """API endpoint which lists the available role permissions for the current user (Requires authentication) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 786ed2c081..2525b3f391 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -20,8 +20,7 @@ logger = logging.getLogger("inventree") class RuleSet(models.Model): - """ - A RuleSet is somewhat like a superset of the django permission class, + """A RuleSet is somewhat like a superset of the django permission class, in that in encapsulates a bunch of permissions. There are *many* apps models used within InvenTree, @@ -222,7 +221,6 @@ class RuleSet(models.Model): @classmethod def check_table_permission(cls, user, table, permission): """Check if the provided user has the specified permission against the table""" - # If the table does *not* require permissions if table in cls.RULESET_IGNORE: return True @@ -253,11 +251,7 @@ class RuleSet(models.Model): @staticmethod def get_model_permission_string(model, permission): - """ - Construct the correctly formatted permission string, - given the app_model name, and the permission type. - """ - + """Construct the correctly formatted permission string, given the app_model name, and the permission type.""" model, app = split_model(model) return "{app}.{perm}_{model}".format( @@ -295,7 +289,6 @@ class RuleSet(models.Model): def get_models(self): """Return the database tables / models that this ruleset covers.""" - return self.RULESET_MODELS.get(self.name, []) @@ -324,8 +317,7 @@ def split_permission(app, perm): def update_group_roles(group, debug=False): - """ - Iterates through all of the RuleSets associated with the group, + """Iterates through all of the RuleSets associated with the group, and ensures that the correct permissions are either applied or removed from the group. This function is called under the following conditions: @@ -335,7 +327,6 @@ def update_group_roles(group, debug=False): The RuleSet model has complete control over the permissions applied to any group. """ - if not canAppAccessDatabase(allow_test=True): return # pragma: no cover @@ -361,15 +352,13 @@ def update_group_roles(group, debug=False): permissions_to_delete = set() def add_model(name, action, allowed): - """ - Add a new model to the pile: + """Add a new model to the pile: args: name - The name of the model e.g. part_part action - The permission action e.g. view allowed - Whether or not the action is allowed """ - if action not in ['view', 'add', 'change', 'delete']: # pragma: no cover raise ValueError("Action {a} is invalid".format(a=action)) @@ -412,16 +401,13 @@ def update_group_roles(group, debug=False): add_model(model, 'delete', ruleset.can_delete) def get_permission_object(permission_string): - """ - Find the permission object in the database, - from the simplified permission string + """Find the permission object in the database, from the simplified permission string Args: permission_string - a simplified permission_string e.g. 'part.view_partcategory' Returns the permission object in the database associated with the permission string """ - (app, perm) = permission_string.split('.') perm, model = split_permission(app, perm) @@ -490,23 +476,19 @@ def update_group_roles(group, debug=False): @receiver(post_save, sender=Group, dispatch_uid='create_missing_rule_sets') def create_missing_rule_sets(sender, instance, **kwargs): - """ - Called *after* a Group object is saved. + """Called *after* a Group object is saved. As the linked RuleSet instances are saved *before* the Group, then we can now use these RuleSet values to update the group permissions. """ - update_group_roles(instance) def check_user_role(user, role, permission): - """ - Check if a user has a particular role:permission combination. + """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 @@ -533,8 +515,7 @@ def check_user_role(user, role, permission): class Owner(models.Model): - """ - The Owner class is a proxy for a Group or User instance. + """The Owner class is a proxy for a Group or User instance. Owner can be associated to any InvenTree model (part, stock, build, etc.) owner_type: Model type (Group or User) @@ -544,13 +525,11 @@ class Owner(models.Model): @classmethod def get_owners_matching_user(cls, user): - """ - Return all "owner" objects matching the provided user: + """Return all "owner" objects matching the provided user: A) An exact match for the user B) Any groups that the user is a part of """ - user_type = ContentType.objects.get(app_label='auth', model='user') group_type = ContentType.objects.get(app_label='auth', model='group') @@ -602,7 +581,6 @@ class Owner(models.Model): @classmethod def create(cls, obj): """Check if owner exist then create new owner entry""" - # Check for existing owner existing_owner = cls.get_owner(obj) @@ -618,7 +596,6 @@ class Owner(models.Model): @classmethod def get_owner(cls, user_or_group): """Get owner instance for a group or user""" - user_model = get_user_model() owner = None content_type_id = 0 @@ -641,11 +618,10 @@ class Owner(models.Model): return owner def get_related_owners(self, include_group=False): - """ - Get all owners "related" to an owner. + """Get all owners "related" to an owner. + This method is useful to retrieve all "user-type" owners linked to a "group-type" owner """ - user_model = get_user_model() related_owners = None @@ -670,21 +646,13 @@ class Owner(models.Model): @receiver(post_save, sender=Group, dispatch_uid='create_owner') @receiver(post_save, sender=get_user_model(), dispatch_uid='create_owner') def create_owner(sender, instance, **kwargs): - """ - Callback function to create a new owner instance - after either a new group or user instance is saved. - """ - + """Callback function to create a new owner instance after either a new group or user instance is saved.""" Owner.create(obj=instance) @receiver(post_delete, sender=Group, dispatch_uid='delete_owner') @receiver(post_delete, sender=get_user_model(), dispatch_uid='delete_owner') def delete_owner(sender, instance, **kwargs): - """ - Callback function to delete an owner instance - after either a new group or user instance is deleted. - """ - + """Callback function to delete an owner instance after either a new group or user instance is deleted.""" owner = Owner.get_owner(instance) owner.delete()