2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-08-09 21:30:54 +00:00

Docstring checks in QC checks (#3089)

* Add pre-commit to the stack

* exclude static

* Add locales to excludes

* fix style errors

* rename pipeline steps

* also wait on precommit

* make template matching simpler

* Use the same code for python setup everywhere

* use step and cache for python setup

* move regular settings up into general envs

* just use full update

* Use invoke instead of static references

* make setup actions more similar

* use python3

* refactor names to be similar

* fix runner version

* fix references

* remove incidential change

* use matrix for os

* Github can't do this right now

* ignore docstyle errors

* Add seperate docstring test

* update flake call

* do not fail on docstring

* refactor setup into workflow

* update reference

* switch to action

* resturcture

* add bash statements

* remove os from cache

* update input checks

* make code cleaner

* fix boolean

* no relative paths

* install wheel by python

* switch to install

* revert back to simple wheel

* refactor import export tests

* move setup keys back to not disturbe tests

* remove docstyle till that is fixed

* update references

* continue on error

* add docstring test

* use relativ action references

* Change step / job docstrings

* update to merge

* reformat comments 1

* fix docstrings 2

* fix docstrings 3

* fix docstrings 4

* fix docstrings 5

* fix docstrings 6

* fix docstrings 7

* fix docstrings 8

* fix docstirns 9

* fix docstrings 10

* docstring adjustments

* update the remaining docstrings

* small docstring changes

* fix function name

* update support files for docstrings

* Add missing args to docstrings

* Remove outdated function

* Add docstrings for the 'build' app

* Make API code cleaner

* add more docstrings for plugin app

* Remove dead code for plugin settings
No idea what that was even intended for

* ignore __init__ files for docstrings

* More docstrings

* Update docstrings for the 'part' directory

* Fixes for related_part functionality

* Fix removed stuff from merge 99676ee

* make more consistent

* Show statistics for docstrings

* add more docstrings

* move specific register statements to make them clearer to understant

* More docstrings for common

* and more docstrings

* and more

* simpler call

* docstrings for notifications

* docstrings for common/tests

* Add docs for common/models

* Revert "move specific register statements to make them clearer to understant"

This reverts commit ca96654622.

* use typing here

* Revert "Make API code cleaner"

This reverts commit 24fb68bd3e.

* docstring updates for the 'users' app

* Add generic Meta info to simple Meta classes

* remove unneeded unique_together statements

* More simple metas

* Remove unnecessary format specifier

* Remove extra json format specifiers

* Add docstrings for the 'plugin' app

* Docstrings for the 'label' app

* Add missing docstrings for the 'report' app

* Fix build test regression

* Fix top-level files

* docstrings for InvenTree/InvenTree

* reduce unneeded code

* add docstrings

* and more docstrings

* more docstrings

* more docstrings for stock

* more docstrings

* docstrings for order/views

* Docstrings for various files in the 'order' app

* Docstrings for order/test_api.py

* Docstrings for order/serializers.py

* Docstrings for order/admin.py

* More docstrings for the order app

* Add docstrings for the 'company' app

* Add unit tests for rebuilding the reference fields

* Prune out some more dead code

* remove more dead code

Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
This commit is contained in:
Matthias Mair
2022-06-01 17:37:39 +02:00
committed by GitHub
parent 66a6915213
commit 0c97a50e47
223 changed files with 4416 additions and 6980 deletions

View File

@@ -1,5 +1,4 @@
"""
The Company module is responsible for managing Company interactions.
"""The Company module is responsible for managing Company interactions.
A company can be either (or both):

View File

@@ -1,3 +1,5 @@
"""Admin class for the 'company' app"""
from django.contrib import admin
import import_export.widgets as widgets
@@ -13,9 +15,10 @@ from .models import (Company, ManufacturerPart, ManufacturerPartAttachment,
class CompanyResource(ModelResource):
""" Class for managing Company data import/export """
"""Class for managing Company data import/export."""
class Meta:
"""Metaclass defines extra options"""
model = Company
skip_unchanged = True
report_skipped = False
@@ -23,6 +26,7 @@ class CompanyResource(ModelResource):
class CompanyAdmin(ImportExportModelAdmin):
"""Admin class for the Company model"""
resource_class = CompanyResource
@@ -35,9 +39,7 @@ class CompanyAdmin(ImportExportModelAdmin):
class SupplierPartResource(ModelResource):
"""
Class for managing SupplierPart data import/export
"""
"""Class for managing SupplierPart data import/export."""
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
@@ -48,6 +50,7 @@ class SupplierPartResource(ModelResource):
supplier_name = Field(attribute='supplier__name', readonly=True)
class Meta:
"""Metaclass defines extra admin options"""
model = SupplierPart
skip_unchanged = True
report_skipped = True
@@ -55,6 +58,7 @@ class SupplierPartResource(ModelResource):
class SupplierPartAdmin(ImportExportModelAdmin):
"""Admin class for the SupplierPart model"""
resource_class = SupplierPartResource
@@ -71,9 +75,7 @@ class SupplierPartAdmin(ImportExportModelAdmin):
class ManufacturerPartResource(ModelResource):
"""
Class for managing ManufacturerPart data import/export
"""
"""Class for managing ManufacturerPart data import/export."""
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part))
@@ -84,6 +86,7 @@ class ManufacturerPartResource(ModelResource):
manufacturer_name = Field(attribute='manufacturer__name', readonly=True)
class Meta:
"""Metaclass defines extra admin options"""
model = ManufacturerPart
skip_unchanged = True
report_skipped = True
@@ -91,9 +94,7 @@ class ManufacturerPartResource(ModelResource):
class ManufacturerPartAdmin(ImportExportModelAdmin):
"""
Admin class for ManufacturerPart model
"""
"""Admin class for ManufacturerPart model."""
resource_class = ManufacturerPartResource
@@ -109,9 +110,7 @@ class ManufacturerPartAdmin(ImportExportModelAdmin):
class ManufacturerPartAttachmentAdmin(ImportExportModelAdmin):
"""
Admin class for ManufacturerPartAttachment model
"""
"""Admin class for ManufacturerPartAttachment model."""
list_display = ('manufacturer_part', 'attachment', 'comment')
@@ -119,11 +118,10 @@ class ManufacturerPartAttachmentAdmin(ImportExportModelAdmin):
class ManufacturerPartParameterResource(ModelResource):
"""
Class for managing ManufacturerPartParameter data import/export
"""
"""Class for managing ManufacturerPartParameter data import/export."""
class Meta:
"""Metaclass defines extra admin options"""
model = ManufacturerPartParameter
skip_unchanged = True
report_skipped = True
@@ -131,9 +129,7 @@ class ManufacturerPartParameterResource(ModelResource):
class ManufacturerPartParameterAdmin(ImportExportModelAdmin):
"""
Admin class for ManufacturerPartParameter model
"""
"""Admin class for ManufacturerPartParameter model."""
resource_class = ManufacturerPartParameterResource
@@ -149,7 +145,7 @@ class ManufacturerPartParameterAdmin(ImportExportModelAdmin):
class SupplierPriceBreakResource(ModelResource):
""" Class for managing SupplierPriceBreak data import/export """
"""Class for managing SupplierPriceBreak data import/export."""
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(SupplierPart))
@@ -164,6 +160,7 @@ class SupplierPriceBreakResource(ModelResource):
MPN = Field(attribute='part__MPN', readonly=True)
class Meta:
"""Metaclass defines extra admin options"""
model = SupplierPriceBreak
skip_unchanged = True
report_skipped = False
@@ -171,6 +168,7 @@ class SupplierPriceBreakResource(ModelResource):
class SupplierPriceBreakAdmin(ImportExportModelAdmin):
"""Admin class for the SupplierPriceBreak model"""
resource_class = SupplierPriceBreakResource

View File

@@ -1,6 +1,4 @@
"""
Provides a JSON API for the Company app
"""
"""Provides a JSON API for the Company app."""
from django.db.models import Q
from django.urls import include, re_path
@@ -23,7 +21,7 @@ from .serializers import (CompanySerializer,
class CompanyList(generics.ListCreateAPIView):
""" API endpoint for accessing a list of Company objects
"""API endpoint for accessing a list of Company objects.
Provides two methods:
@@ -35,7 +33,7 @@ class CompanyList(generics.ListCreateAPIView):
queryset = Company.objects.all()
def get_queryset(self):
"""Return annotated queryset for the company list endpoint"""
queryset = super().get_queryset()
queryset = CompanySerializer.annotate_queryset(queryset)
@@ -70,13 +68,13 @@ class CompanyList(generics.ListCreateAPIView):
class CompanyDetail(generics.RetrieveUpdateDestroyAPIView):
""" API endpoint for detail of a single Company object """
"""API endpoint for detail of a single Company object."""
queryset = Company.objects.all()
serializer_class = CompanySerializer
def get_queryset(self):
"""Return annotated queryset for the company detail endpoint"""
queryset = super().get_queryset()
queryset = CompanySerializer.annotate_queryset(queryset)
@@ -84,11 +82,11 @@ class CompanyDetail(generics.RetrieveUpdateDestroyAPIView):
class ManufacturerPartFilter(rest_filters.FilterSet):
"""
Custom API filters for the ManufacturerPart list endpoint.
"""
"""Custom API filters for the ManufacturerPart list endpoint."""
class Meta:
"""Metaclass options."""
model = ManufacturerPart
fields = [
'manufacturer',
@@ -101,7 +99,7 @@ class ManufacturerPartFilter(rest_filters.FilterSet):
class ManufacturerPartList(generics.ListCreateAPIView):
""" API endpoint for list view of ManufacturerPart object
"""API endpoint for list view of ManufacturerPart object.
- GET: Return list of ManufacturerPart objects
- POST: Create a new ManufacturerPart object
@@ -117,7 +115,7 @@ class ManufacturerPartList(generics.ListCreateAPIView):
filterset_class = ManufacturerPartFilter
def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint"""
# Do we wish to include extra detail?
try:
params = self.request.query_params
@@ -149,7 +147,7 @@ class ManufacturerPartList(generics.ListCreateAPIView):
class ManufacturerPartDetail(generics.RetrieveUpdateDestroyAPIView):
""" API endpoint for detail view of ManufacturerPart object
"""API endpoint for detail view of ManufacturerPart object.
- GET: Retrieve detail view
- PATCH: Update object
@@ -161,9 +159,7 @@ class ManufacturerPartDetail(generics.RetrieveUpdateDestroyAPIView):
class ManufacturerPartAttachmentList(AttachmentMixin, generics.ListCreateAPIView):
"""
API endpoint for listing (and creating) a ManufacturerPartAttachment (file upload).
"""
"""API endpoint for listing (and creating) a ManufacturerPartAttachment (file upload)."""
queryset = ManufacturerPartAttachment.objects.all()
serializer_class = ManufacturerPartAttachmentSerializer
@@ -178,24 +174,20 @@ class ManufacturerPartAttachmentList(AttachmentMixin, generics.ListCreateAPIView
class ManufacturerPartAttachmentDetail(AttachmentMixin, generics.RetrieveUpdateDestroyAPIView):
"""
Detail endpooint for ManufacturerPartAttachment model
"""
"""Detail endpooint for ManufacturerPartAttachment model."""
queryset = ManufacturerPartAttachment.objects.all()
serializer_class = ManufacturerPartAttachmentSerializer
class ManufacturerPartParameterList(generics.ListCreateAPIView):
"""
API endpoint for list view of ManufacturerPartParamater model.
"""
"""API endpoint for list view of ManufacturerPartParamater model."""
queryset = ManufacturerPartParameter.objects.all()
serializer_class = ManufacturerPartParameterSerializer
def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint"""
# Do we wish to include any extra detail?
try:
params = self.request.query_params
@@ -215,10 +207,7 @@ class ManufacturerPartParameterList(generics.ListCreateAPIView):
return self.serializer_class(*args, **kwargs)
def filter_queryset(self, queryset):
"""
Custom filtering for the queryset
"""
"""Custom filtering for the queryset."""
queryset = super().filter_queryset(queryset)
params = self.request.query_params
@@ -258,16 +247,14 @@ class ManufacturerPartParameterList(generics.ListCreateAPIView):
class ManufacturerPartParameterDetail(generics.RetrieveUpdateDestroyAPIView):
"""
API endpoint for detail view of ManufacturerPartParameter model
"""
"""API endpoint for detail view of ManufacturerPartParameter model."""
queryset = ManufacturerPartParameter.objects.all()
serializer_class = ManufacturerPartParameterSerializer
class SupplierPartList(generics.ListCreateAPIView):
""" API endpoint for list view of SupplierPart object
"""API endpoint for list view of SupplierPart object.
- GET: Return list of SupplierPart objects
- POST: Create a new SupplierPart object
@@ -275,17 +262,8 @@ class SupplierPartList(generics.ListCreateAPIView):
queryset = SupplierPart.objects.all()
def get_queryset(self):
queryset = super().get_queryset()
return queryset
def filter_queryset(self, queryset):
"""
Custom filtering for the queryset.
"""
"""Custom filtering for the queryset."""
queryset = super().filter_queryset(queryset)
params = self.request.query_params
@@ -330,6 +308,7 @@ class SupplierPartList(generics.ListCreateAPIView):
return queryset
def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint"""
# Do we wish to include extra detail?
try:
@@ -369,7 +348,7 @@ class SupplierPartList(generics.ListCreateAPIView):
class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView):
""" API endpoint for detail view of SupplierPart object
"""API endpoint for detail view of SupplierPart object.
- GET: Retrieve detail view
- PATCH: Update object
@@ -384,7 +363,7 @@ class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView):
class SupplierPriceBreakList(generics.ListCreateAPIView):
""" API endpoint for list view of SupplierPriceBreak object
"""API endpoint for list view of SupplierPriceBreak object.
- GET: Retrieve list of SupplierPriceBreak objects
- POST: Create a new SupplierPriceBreak object
@@ -403,9 +382,7 @@ class SupplierPriceBreakList(generics.ListCreateAPIView):
class SupplierPriceBreakDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Detail endpoint for SupplierPriceBreak object
"""
"""Detail endpoint for SupplierPriceBreak object."""
queryset = SupplierPriceBreak.objects.all()
serializer_class = SupplierPriceBreakSerializer

View File

@@ -1,12 +1,13 @@
"""Config for the 'company' app"""
from django.apps import AppConfig
class CompanyConfig(AppConfig):
"""Config class for the 'company' app"""
name = 'company'
def ready(self):
"""
This function is called whenever the Company app is loaded.
"""
"""This function is called whenever the Company app is loaded."""
pass

View File

@@ -1,6 +1,4 @@
"""
Django Forms for interacting with Company app
"""
"""Django Forms for interacting with Company app."""
import django.forms
from django.utils.translation import gettext_lazy as _
@@ -12,9 +10,7 @@ from .models import Company, SupplierPriceBreak
class CompanyImageDownloadForm(HelperForm):
"""
Form for downloading an image from a URL
"""
"""Form for downloading an image from a URL."""
url = django.forms.URLField(
label=_('URL'),
@@ -23,6 +19,8 @@ class CompanyImageDownloadForm(HelperForm):
)
class Meta:
"""Metaclass options."""
model = Company
fields = [
'url',
@@ -30,7 +28,7 @@ class CompanyImageDownloadForm(HelperForm):
class EditPriceBreakForm(HelperForm):
""" Form for creating / editing a supplier price break """
"""Form for creating / editing a supplier price break."""
quantity = RoundingDecimalFormField(
max_digits=10,
@@ -40,6 +38,8 @@ class EditPriceBreakForm(HelperForm):
)
class Meta:
"""Metaclass options."""
model = SupplierPriceBreak
fields = [
'part',

View File

@@ -1,6 +1,4 @@
"""
Company database model definitions
"""
"""Company database model definitions."""
import os
@@ -27,7 +25,7 @@ from InvenTree.status_codes import PurchaseOrderStatus
def rename_company_image(instance, filename):
""" Function to rename a company image after upload
"""Function to rename a company image after upload.
Args:
instance: Company object
@@ -36,7 +34,6 @@ def rename_company_image(instance, filename):
Returns:
New image filename
"""
base = 'company_images'
if filename.count('.') > 0:
@@ -53,7 +50,8 @@ def rename_company_image(instance, filename):
class Company(models.Model):
""" A Company object represents an external company.
"""A Company object represents an external company.
It may be a supplier or a customer or a manufacturer (or a combination)
- A supplier is a company from which parts can be purchased
@@ -79,9 +77,11 @@ class Company(models.Model):
@staticmethod
def get_api_url():
"""Return the API URL associated with the Company model"""
return reverse('api-company-list')
class Meta:
"""Metaclass defines extra model options"""
ordering = ['name', ]
constraints = [
UniqueConstraint(fields=['name', 'email'], name='unique_name_email_pair')
@@ -150,13 +150,11 @@ class Company(models.Model):
@property
def currency_code(self):
"""
Return the currency code associated with this company.
"""Return the currency code associated with this company.
- If the currency code is invalid, use the default currency
- If the currency code is not specified, use the default currency
"""
code = self.currency
if code not in CURRENCIES:
@@ -165,103 +163,41 @@ class Company(models.Model):
return code
def __str__(self):
""" Get string representation of a Company """
"""Get string representation of a Company."""
return "{n} - {d}".format(n=self.name, d=self.description)
def get_absolute_url(self):
""" Get the web URL for the detail view for this Company """
"""Get the web URL for the detail view for this Company."""
return reverse('company-detail', kwargs={'pk': self.id})
def get_image_url(self):
""" Return the URL of the image for this company """
"""Return the URL of the image for this company."""
if self.image:
return getMediaUrl(self.image.url)
else:
return getBlankImage()
def get_thumbnail_url(self):
""" Return the URL for the thumbnail image for this Company """
"""Return the URL for the thumbnail image for this Company."""
if self.image:
return getMediaUrl(self.image.thumbnail.url)
else:
return getBlankThumbnail()
@property
def manufactured_part_count(self):
""" The number of parts manufactured by this company """
return self.manufactured_parts.count()
@property
def has_manufactured_parts(self):
return self.manufactured_part_count > 0
@property
def supplied_part_count(self):
""" The number of parts supplied by this company """
return self.supplied_parts.count()
@property
def has_supplied_parts(self):
""" Return True if this company supplies any parts """
return self.supplied_part_count > 0
@property
def parts(self):
""" Return SupplierPart objects which are supplied or manufactured by this company """
"""Return SupplierPart objects which are supplied or manufactured by this company."""
return SupplierPart.objects.filter(Q(supplier=self.id) | Q(manufacturer_part__manufacturer=self.id))
@property
def part_count(self):
""" The number of parts manufactured (or supplied) by this Company """
return self.parts.count()
@property
def has_parts(self):
return self.part_count > 0
@property
def stock_items(self):
""" Return a list of all stock items supplied or manufactured by this company """
"""Return a list of all stock items supplied or manufactured by this company."""
stock = apps.get_model('stock', 'StockItem')
return stock.objects.filter(Q(supplier_part__supplier=self.id) | Q(supplier_part__manufacturer_part__manufacturer=self.id)).all()
@property
def stock_count(self):
""" Return the number of stock items supplied or manufactured by this company """
return self.stock_items.count()
def outstanding_purchase_orders(self):
""" Return purchase orders which are 'outstanding' """
return self.purchase_orders.filter(status__in=PurchaseOrderStatus.OPEN)
def pending_purchase_orders(self):
""" Return purchase orders which are PENDING (not yet issued) """
return self.purchase_orders.filter(status=PurchaseOrderStatus.PENDING)
def closed_purchase_orders(self):
""" Return purchase orders which are not 'outstanding'
- Complete
- Failed / lost
- Returned
"""
return self.purchase_orders.exclude(status__in=PurchaseOrderStatus.OPEN)
def complete_purchase_orders(self):
return self.purchase_orders.filter(status=PurchaseOrderStatus.COMPLETE)
def failed_purchase_orders(self):
""" Return any purchase orders which were not successful """
return self.purchase_orders.filter(status__in=PurchaseOrderStatus.FAILED)
class Contact(models.Model):
""" A Contact represents a person who works at a particular company.
A Company may have zero or more associated Contact objects.
"""A Contact represents a person who works at a particular company. A Company may have zero or more associated Contact objects.
Attributes:
company: Company link for this contact
@@ -284,10 +220,7 @@ class Contact(models.Model):
class ManufacturerPart(models.Model):
""" Represents a unique part as provided by a Manufacturer
Each ManufacturerPart is identified by a MPN (Manufacturer Part Number)
Each ManufacturerPart is also linked to a Part object.
A Part may be available from multiple manufacturers
"""Represents a unique part as provided by a Manufacturer Each ManufacturerPart is identified by a MPN (Manufacturer Part Number) Each ManufacturerPart is also linked to a Part object. A Part may be available from multiple manufacturers.
Attributes:
part: Link to the master Part
@@ -299,9 +232,11 @@ class ManufacturerPart(models.Model):
@staticmethod
def get_api_url():
"""Return the API URL associated with the ManufacturerPart instance"""
return reverse('api-manufacturer-part-list')
class Meta:
"""Metaclass defines extra model options"""
unique_together = ('part', 'manufacturer', 'MPN')
part = models.ForeignKey('part.Part', on_delete=models.CASCADE,
@@ -346,10 +281,7 @@ class ManufacturerPart(models.Model):
@classmethod
def create(cls, part, manufacturer, mpn, description, link=None):
""" Check if ManufacturerPart instance does not already exist
then create it
"""
"""Check if ManufacturerPart instance does not already exist then create it."""
manufacturer_part = None
try:
@@ -364,6 +296,7 @@ class ManufacturerPart(models.Model):
return manufacturer_part
def __str__(self):
"""Format a string representation of a ManufacturerPart"""
s = ''
if self.manufacturer:
@@ -376,15 +309,15 @@ class ManufacturerPart(models.Model):
class ManufacturerPartAttachment(InvenTreeAttachment):
"""
Model for storing file attachments against a ManufacturerPart object
"""
"""Model for storing file attachments against a ManufacturerPart object."""
@staticmethod
def get_api_url():
"""Return the API URL associated with the ManufacturerPartAttachment model"""
return reverse('api-manufacturer-part-attachment-list')
def getSubdir(self):
"""Return the subdirectory where attachment files for the ManufacturerPart model are located"""
return os.path.join("manufacturer_part_files", str(self.manufacturer_part.id))
manufacturer_part = models.ForeignKey(ManufacturerPart, on_delete=models.CASCADE,
@@ -392,8 +325,7 @@ class ManufacturerPartAttachment(InvenTreeAttachment):
class ManufacturerPartParameter(models.Model):
"""
A ManufacturerPartParameter represents a key:value parameter for a MnaufacturerPart.
"""A ManufacturerPartParameter represents a key:value parameter for a MnaufacturerPart.
This is used to represent parmeters / properties for a particular manufacturer part.
@@ -402,9 +334,11 @@ class ManufacturerPartParameter(models.Model):
@staticmethod
def get_api_url():
"""Return the API URL associated with the ManufacturerPartParameter model"""
return reverse('api-manufacturer-part-parameter-list')
class Meta:
"""Metaclass defines extra model options"""
unique_together = ('manufacturer_part', 'name')
manufacturer_part = models.ForeignKey(
@@ -437,13 +371,14 @@ class ManufacturerPartParameter(models.Model):
class SupplierPartManager(models.Manager):
""" Define custom SupplierPart objects manager
"""Define custom SupplierPart objects manager.
The main purpose of this manager is to improve database hit as the
SupplierPart model involves A LOT of foreign keys lookups
The main purpose of this manager is to improve database hit as the
SupplierPart model involves A LOT of foreign keys lookups
"""
def get_queryset(self):
"""Prefetch related fields when querying against the SupplierPart model"""
# Always prefetch related models
return super().get_queryset().prefetch_related(
'part',
@@ -453,10 +388,7 @@ class SupplierPartManager(models.Manager):
class SupplierPart(models.Model):
""" Represents a unique part as provided by a Supplier
Each SupplierPart is identified by a SKU (Supplier Part Number)
Each SupplierPart is also linked to a Part or ManufacturerPart object.
A Part may be available from multiple suppliers
"""Represents a unique part as provided by a Supplier Each SupplierPart is identified by a SKU (Supplier Part Number) Each SupplierPart is also linked to a Part or ManufacturerPart object. A Part may be available from multiple suppliers.
Attributes:
part: Link to the master Part (Obsolete)
@@ -476,13 +408,15 @@ class SupplierPart(models.Model):
@staticmethod
def get_api_url():
"""Return the API URL associated with the SupplierPart model"""
return reverse('api-supplier-part-list')
def get_absolute_url(self):
"""Return the web URL of the detail view for this SupplierPart"""
return reverse('supplier-part-detail', kwargs={'pk': self.id})
def api_instance_filters(self):
"""Return custom API filters for this particular instance"""
return {
'manufacturer_part': {
'part': self.part.pk
@@ -490,13 +424,17 @@ class SupplierPart(models.Model):
}
class Meta:
"""Metaclass defines extra model options"""
unique_together = ('part', 'supplier', 'SKU')
# This model was moved from the 'Part' app
db_table = 'part_supplierpart'
def clean(self):
"""Custom clean action for the SupplierPart model:
- Ensure that manufacturer_part.part and part are the same!
"""
super().clean()
# Ensure that the linked manufacturer_part points to the same part!
@@ -508,8 +446,7 @@ class SupplierPart(models.Model):
})
def save(self, *args, **kwargs):
""" Overriding save method to connect an existing ManufacturerPart """
"""Overriding save method to connect an existing ManufacturerPart."""
manufacturer_part = None
if all(key in kwargs for key in ('manufacturer', 'MPN')):
@@ -593,10 +530,10 @@ class SupplierPart(models.Model):
@property
def manufacturer_string(self):
""" Format a MPN string for this SupplierPart.
"""Format a MPN string for this SupplierPart.
Concatenates manufacture name and part number.
"""
items = []
if self.manufacturer_part:
@@ -609,26 +546,26 @@ class SupplierPart(models.Model):
@property
def has_price_breaks(self):
"""Return True if this SupplierPart has associated price breaks"""
return self.price_breaks.count() > 0
@property
def price_breaks(self):
""" Return the associated price breaks in the correct order """
"""Return the associated price breaks in the correct order."""
return self.pricebreaks.order_by('quantity').all()
@property
def unit_pricing(self):
"""Return the single-quantity pricing for this SupplierPart"""
return self.get_price(1)
def add_price_break(self, quantity, price):
"""
Create a new price break for this part
def add_price_break(self, quantity, price) -> None:
"""Create a new price break for this part.
args:
quantity - Numerical quantity
price - Must be a Money object
Args:
quantity: Numerical quantity
price: Must be a Money object
"""
# Check if a price break at that quantity already exists...
if self.price_breaks.filter(quantity=quantity, part=self.pk).exists():
return
@@ -642,18 +579,14 @@ class SupplierPart(models.Model):
get_price = common.models.get_price
def open_orders(self):
""" Return a database query for PurchaseOrder line items for this SupplierPart,
limited to purchase orders that are open / outstanding.
"""
"""Return a database query for PurchaseOrder line items for this SupplierPart, limited to purchase orders that are open / outstanding."""
return self.purchase_order_line_items.prefetch_related('order').filter(order__status__in=PurchaseOrderStatus.OPEN)
def on_order(self):
""" Return the total quantity of items currently on order.
"""Return the total quantity of items currently on order.
Subtract partially received stock as appropriate
"""
totals = self.open_orders().aggregate(Sum('quantity'), Sum('received'))
# Quantity on order
@@ -668,15 +601,16 @@ class SupplierPart(models.Model):
return max(q - r, 0)
def purchase_orders(self):
""" Returns a list of purchase orders relating to this supplier part """
"""Returns a list of purchase orders relating to this supplier part."""
return [line.order for line in self.purchase_order_line_items.all().prefetch_related('order')]
@property
def pretty_name(self):
"""Format a 'pretty' name for this SupplierPart"""
return str(self)
def __str__(self):
"""Format a string representation of a SupplierPart"""
s = ''
if self.part.IPN:
@@ -692,7 +626,8 @@ class SupplierPart(models.Model):
class SupplierPriceBreak(common.models.PriceBreak):
""" Represents a quantity price break for a SupplierPart.
"""Represents a quantity price break for a SupplierPart.
- Suppliers can offer discounts at larger quantities
- SupplierPart(s) may have zero-or-more associated SupplierPriceBreak(s)
@@ -706,6 +641,7 @@ class SupplierPriceBreak(common.models.PriceBreak):
@staticmethod
def get_api_url():
"""Return the API URL associated with the SupplierPriceBreak model"""
return reverse('api-part-supplier-price-list')
part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='pricebreaks', verbose_name=_('Part'),)
@@ -713,10 +649,12 @@ class SupplierPriceBreak(common.models.PriceBreak):
updated = models.DateTimeField(auto_now=True, null=True, verbose_name=_('last updated'))
class Meta:
"""Metaclass defines extra model options"""
unique_together = ("part", "quantity")
# This model was moved from the 'Part' app
db_table = 'part_supplierpricebreak'
def __str__(self):
"""Format a string representation of a SupplierPriceBreak instance"""
return f'{self.part.SKU} - {self.price} @ {self.quantity}'

View File

@@ -1,6 +1,4 @@
"""
JSON serializers for Company app
"""
"""JSON serializers for Company app."""
from django.utils.translation import gettext_lazy as _
@@ -21,13 +19,15 @@ from .models import (Company, ManufacturerPart, ManufacturerPartAttachment,
class CompanyBriefSerializer(InvenTreeModelSerializer):
""" Serializer for Company object (limited detail) """
"""Serializer for Company object (limited detail)"""
url = serializers.CharField(source='get_absolute_url', read_only=True)
image = serializers.CharField(source='get_thumbnail_url', read_only=True)
class Meta:
"""Metaclass options."""
model = Company
fields = [
'pk',
@@ -39,11 +39,11 @@ class CompanyBriefSerializer(InvenTreeModelSerializer):
class CompanySerializer(InvenTreeModelSerializer):
""" Serializer for Company object (full detail) """
"""Serializer for Company object (full detail)"""
@staticmethod
def annotate_queryset(queryset):
"""Annoate the supplied queryset with aggregated information"""
# Add count of parts manufactured
queryset = queryset.annotate(
parts_manufactured=SubqueryCount('manufactured_parts')
@@ -71,6 +71,8 @@ class CompanySerializer(InvenTreeModelSerializer):
)
class Meta:
"""Metaclass options."""
model = Company
fields = [
'pk',
@@ -96,9 +98,7 @@ class CompanySerializer(InvenTreeModelSerializer):
class ManufacturerPartSerializer(InvenTreeModelSerializer):
"""
Serializer for ManufacturerPart object
"""
"""Serializer for ManufacturerPart object."""
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
@@ -107,7 +107,7 @@ class ManufacturerPartSerializer(InvenTreeModelSerializer):
pretty_name = serializers.CharField(read_only=True)
def __init__(self, *args, **kwargs):
"""Initialize this serializer with extra detail fields as required"""
part_detail = kwargs.pop('part_detail', True)
manufacturer_detail = kwargs.pop('manufacturer_detail', True)
prettify = kwargs.pop('pretty', False)
@@ -126,6 +126,8 @@ class ManufacturerPartSerializer(InvenTreeModelSerializer):
manufacturer = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_manufacturer=True))
class Meta:
"""Metaclass options."""
model = ManufacturerPart
fields = [
'pk',
@@ -141,11 +143,11 @@ class ManufacturerPartSerializer(InvenTreeModelSerializer):
class ManufacturerPartAttachmentSerializer(InvenTreeAttachmentSerializer):
"""
Serializer for the ManufacturerPartAttachment class
"""
"""Serializer for the ManufacturerPartAttachment class."""
class Meta:
"""Metaclass options."""
model = ManufacturerPartAttachment
fields = [
@@ -164,14 +166,12 @@ class ManufacturerPartAttachmentSerializer(InvenTreeAttachmentSerializer):
class ManufacturerPartParameterSerializer(InvenTreeModelSerializer):
"""
Serializer for the ManufacturerPartParameter model
"""
"""Serializer for the ManufacturerPartParameter model."""
manufacturer_part_detail = ManufacturerPartSerializer(source='manufacturer_part', many=False, read_only=True)
def __init__(self, *args, **kwargs):
"""Initialize this serializer with extra detail fields as required"""
man_detail = kwargs.pop('manufacturer_part_detail', False)
super(ManufacturerPartParameterSerializer, self).__init__(*args, **kwargs)
@@ -180,6 +180,8 @@ class ManufacturerPartParameterSerializer(InvenTreeModelSerializer):
self.fields.pop('manufacturer_part_detail')
class Meta:
"""Metaclass options."""
model = ManufacturerPartParameter
fields = [
@@ -193,7 +195,7 @@ class ManufacturerPartParameterSerializer(InvenTreeModelSerializer):
class SupplierPartSerializer(InvenTreeModelSerializer):
""" Serializer for SupplierPart object """
"""Serializer for SupplierPart object."""
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
@@ -204,7 +206,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
pretty_name = serializers.CharField(read_only=True)
def __init__(self, *args, **kwargs):
"""Initialize this serializer with extra detail fields as required"""
part_detail = kwargs.pop('part_detail', True)
supplier_detail = kwargs.pop('supplier_detail', True)
manufacturer_detail = kwargs.pop('manufacturer_detail', True)
@@ -234,6 +236,8 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
manufacturer_part_detail = ManufacturerPartSerializer(source='manufacturer_part', read_only=True)
class Meta:
"""Metaclass options."""
model = SupplierPart
fields = [
'description',
@@ -255,8 +259,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
]
def create(self, validated_data):
""" Extract manufacturer data and process ManufacturerPart """
"""Extract manufacturer data and process ManufacturerPart."""
# Create SupplierPart
supplier_part = super().create(validated_data)
@@ -275,7 +278,7 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
""" Serializer for SupplierPriceBreak object """
"""Serializer for SupplierPriceBreak object."""
quantity = InvenTreeDecimalField()
@@ -292,6 +295,8 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
)
class Meta:
"""Metaclass options."""
model = SupplierPriceBreak
fields = [
'pk',

View File

@@ -1,3 +1,5 @@
"""Unit testing for the company app API functions"""
from django.urls import reverse
from rest_framework import status
@@ -8,9 +10,7 @@ from .models import Company
class CompanyTest(InvenTreeAPITestCase):
"""
Series of tests for the Company DRF API
"""
"""Series of tests for the Company DRF API."""
roles = [
'purchase_order.add',
@@ -18,7 +18,7 @@ class CompanyTest(InvenTreeAPITestCase):
]
def setUp(self):
"""Perform initialization for the unit test class"""
super().setUp()
self.acme = Company.objects.create(name='ACME', description='Supplier', is_customer=False, is_supplier=True)
@@ -26,6 +26,7 @@ class CompanyTest(InvenTreeAPITestCase):
Company.objects.create(name='Sippy Cup Emporium', description='Another supplier')
def test_company_list(self):
"""Test the list API endpoint for the Company model"""
url = reverse('api-company-list')
# There should be three companies
@@ -45,10 +46,7 @@ class CompanyTest(InvenTreeAPITestCase):
self.assertEqual(len(response.data), 2)
def test_company_detail(self):
"""
Tests for the Company detail endpoint
"""
"""Tests for the Company detail endpoint."""
url = reverse('api-company-detail', kwargs={'pk': self.acme.pk})
response = self.get(url)
@@ -71,20 +69,14 @@ class CompanyTest(InvenTreeAPITestCase):
self.assertEqual(response.data['currency'], 'NZD')
def test_company_search(self):
"""
Test search functionality in company list
"""
"""Test search functionality in company list."""
url = reverse('api-company-list')
data = {'search': 'cup'}
response = self.get(url, data)
self.assertEqual(len(response.data), 2)
def test_company_create(self):
"""
Test that we can create a company via the API!
"""
"""Test that we can create a company via the API!"""
url = reverse('api-company-list')
# Name is required
@@ -146,9 +138,7 @@ class CompanyTest(InvenTreeAPITestCase):
class ManufacturerTest(InvenTreeAPITestCase):
"""
Series of tests for the Manufacturer DRF API
"""
"""Series of tests for the Manufacturer DRF API."""
fixtures = [
'category',
@@ -164,6 +154,7 @@ class ManufacturerTest(InvenTreeAPITestCase):
]
def test_manufacturer_part_list(self):
"""Test the ManufacturerPart API list functionality"""
url = reverse('api-manufacturer-part-list')
# There should be three manufacturer parts
@@ -191,9 +182,7 @@ class ManufacturerTest(InvenTreeAPITestCase):
self.assertEqual(len(response.data), 2)
def test_manufacturer_part_detail(self):
"""
Tests for the ManufacturerPart detail endpoint
"""
"""Tests for the ManufacturerPart detail endpoint."""
url = reverse('api-manufacturer-part-detail', kwargs={'pk': 1})
response = self.get(url)
@@ -210,13 +199,14 @@ class ManufacturerTest(InvenTreeAPITestCase):
self.assertEqual(response.data['MPN'], 'MPN-TEST-123')
def test_manufacturer_part_search(self):
# Test search functionality in manufacturer list
"""Test search functionality in manufacturer list"""
url = reverse('api-manufacturer-part-list')
data = {'search': 'MPN'}
response = self.get(url, data)
self.assertEqual(len(response.data), 3)
def test_supplier_part_create(self):
"""Test a SupplierPart can be created via the API"""
url = reverse('api-supplier-part-list')
# Create a manufacturer part

View File

@@ -1,6 +1,4 @@
"""
Tests for the company model database migrations
"""
"""Tests for the company model database migrations."""
from django_test_migrations.contrib.unittest_case import MigratorTestCase
@@ -8,15 +6,13 @@ from InvenTree import helpers
class TestForwardMigrations(MigratorTestCase):
"""Unit testing class for testing 'company' app migrations"""
migrate_from = ('company', helpers.getOldestMigrationFile('company'))
migrate_to = ('company', helpers.getNewestMigrationFile('company'))
def prepare(self):
"""
Create some simple Company data, and ensure that it migrates OK
"""
"""Create some simple Company data, and ensure that it migrates OK."""
Company = self.old_state.apps.get_model('company', 'company')
Company.objects.create(
@@ -26,29 +22,25 @@ class TestForwardMigrations(MigratorTestCase):
)
def test_migrations(self):
"""Test the database state after applying all migrations"""
Company = self.new_state.apps.get_model('company', 'company')
self.assertEqual(Company.objects.count(), 1)
class TestManufacturerField(MigratorTestCase):
"""
Tests for migration 0019 which migrates from old 'manufacturer_name' field to new 'manufacturer' field
"""
"""Tests for migration 0019 which migrates from old 'manufacturer_name' field to new 'manufacturer' field."""
migrate_from = ('company', '0018_supplierpart_manufacturer')
migrate_to = ('company', '0019_auto_20200413_0642')
def prepare(self):
"""
Prepare the database by adding some test data 'before' the change:
"""Prepare the database by adding some test data 'before' the change:
- Part object
- Company object (supplier)
- SupplierPart object
"""
Part = self.old_state.apps.get_model('part', 'part')
Company = self.old_state.apps.get_model('company', 'company')
SupplierPart = self.old_state.apps.get_model('company', 'supplierpart')
@@ -85,10 +77,7 @@ class TestManufacturerField(MigratorTestCase):
self.assertEqual(Company.objects.count(), 1)
def test_company_objects(self):
"""
Test that the new companies have been created successfully
"""
"""Test that the new companies have been created successfully."""
# Two additional company objects should have been created
Company = self.new_state.apps.get_model('company', 'company')
self.assertEqual(Company.objects.count(), 3)
@@ -108,22 +97,18 @@ class TestManufacturerField(MigratorTestCase):
class TestManufacturerPart(MigratorTestCase):
"""
Tests for migration 0034-0037 which added and transitioned to the ManufacturerPart model
"""
"""Tests for migration 0034-0037 which added and transitioned to the ManufacturerPart model."""
migrate_from = ('company', '0033_auto_20210410_1528')
migrate_to = ('company', '0037_supplierpart_update_3')
def prepare(self):
"""
Prepare the database by adding some test data 'before' the change:
"""Prepare the database by adding some test data 'before' the change:
- Part object
- Company object (supplier)
- SupplierPart object
"""
Part = self.old_state.apps.get_model('part', 'part')
Company = self.old_state.apps.get_model('company', 'company')
SupplierPart = self.old_state.apps.get_model('company', 'supplierpart')
@@ -214,10 +199,7 @@ class TestManufacturerPart(MigratorTestCase):
)
def test_manufacturer_part_objects(self):
"""
Test that the new companies have been created successfully
"""
"""Test that the new companies have been created successfully."""
# Check on the SupplierPart objects
SupplierPart = self.new_state.apps.get_model('company', 'supplierpart')
@@ -238,16 +220,13 @@ class TestManufacturerPart(MigratorTestCase):
class TestCurrencyMigration(MigratorTestCase):
"""
Tests for upgrade from basic currency support to django-money
"""
"""Tests for upgrade from basic currency support to django-money."""
migrate_from = ('company', '0025_auto_20201110_1001')
migrate_to = ('company', '0026_auto_20201110_1011')
def prepare(self):
"""
Prepare some data:
"""Prepare some data:
- A part to buy
- A supplier to buy from
@@ -255,7 +234,6 @@ class TestCurrencyMigration(MigratorTestCase):
- Multiple currency objects
- Multiple supplier price breaks
"""
Part = self.old_state.apps.get_model('part', 'part')
part = Part.objects.create(
@@ -293,7 +271,7 @@ class TestCurrencyMigration(MigratorTestCase):
self.assertIsNone(pb.price)
def test_currency_migration(self):
"""Test database state after applying migrations"""
PB = self.new_state.apps.get_model('company', 'supplierpricebreak')
for pb in PB.objects.all():

View File

@@ -1,11 +1,12 @@
""" Unit tests for Company views (see views.py) """
"""Unit tests for Company views (see views.py)"""
from django.urls import reverse
from InvenTree.helpers import InvenTreeTestCase
class CompanyViewTestBase(InvenTreeTestCase):
class CompanyViewTest(InvenTreeTestCase):
"""Tests for various 'Company' views."""
fixtures = [
'category',
@@ -18,40 +19,29 @@ class CompanyViewTestBase(InvenTreeTestCase):
roles = 'all'
class CompanyViewTest(CompanyViewTestBase):
"""
Tests for various 'Company' views
"""
def test_company_index(self):
""" Test the company index """
"""Test the company index."""
response = self.client.get(reverse('company-index'))
self.assertEqual(response.status_code, 200)
def test_manufacturer_index(self):
""" Test the manufacturer index """
"""Test the manufacturer index."""
response = self.client.get(reverse('manufacturer-index'))
self.assertEqual(response.status_code, 200)
def test_customer_index(self):
""" Test the customer index """
"""Test the customer index."""
response = self.client.get(reverse('customer-index'))
self.assertEqual(response.status_code, 200)
def test_manufacturer_part_detail_view(self):
""" Test the manufacturer part detail view """
"""Test the manufacturer part detail view."""
response = self.client.get(reverse('manufacturer-part-detail', kwargs={'pk': 1}))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'MPN123')
def test_supplier_part_detail_view(self):
""" Test the supplier part detail view """
"""Test the supplier part detail view."""
response = self.client.get(reverse('supplier-part-detail', kwargs={'pk': 10}))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'MPN456-APPEL')

View File

@@ -1,3 +1,5 @@
"""Unit tests for the models in the 'company' app"""
import os
from decimal import Decimal
@@ -11,6 +13,7 @@ from .models import (Company, Contact, ManufacturerPart, SupplierPart,
class CompanySimpleTest(TestCase):
"""Unit tests for the Company model"""
fixtures = [
'company',
@@ -24,6 +27,7 @@ class CompanySimpleTest(TestCase):
]
def setUp(self):
"""Perform initialization for the tests in this class"""
Company.objects.create(name='ABC Co.',
description='Seller of ABC products',
website='www.abc-sales.com',
@@ -37,15 +41,18 @@ class CompanySimpleTest(TestCase):
self.zergm312 = SupplierPart.objects.get(SKU='ZERGM312')
def test_company_model(self):
"""Tests for the company model data"""
c = Company.objects.get(name='ABC Co.')
self.assertEqual(c.name, 'ABC Co.')
self.assertEqual(str(c), 'ABC Co. - Seller of ABC products')
def test_company_url(self):
"""Test the detail URL for a company"""
c = Company.objects.get(pk=1)
self.assertEqual(c.get_absolute_url(), '/company/1/')
def test_image_renamer(self):
"""Test the company image upload functionality"""
c = Company.objects.get(pk=1)
rn = rename_company_image(c, 'test.png')
self.assertEqual(rn, 'company_images' + os.path.sep + 'company_1_img.png')
@@ -53,23 +60,8 @@ class CompanySimpleTest(TestCase):
rn = rename_company_image(c, 'test2')
self.assertEqual(rn, 'company_images' + os.path.sep + 'company_1_img')
def test_part_count(self):
acme = Company.objects.get(pk=1)
appel = Company.objects.get(pk=2)
zerg = Company.objects.get(pk=3)
self.assertTrue(acme.has_parts)
self.assertEqual(acme.supplied_part_count, 4)
self.assertTrue(appel.has_parts)
self.assertEqual(appel.supplied_part_count, 4)
self.assertTrue(zerg.has_parts)
self.assertEqual(zerg.supplied_part_count, 2)
def test_price_breaks(self):
"""Unit tests for price breaks"""
self.assertTrue(self.acme0001.has_price_breaks)
self.assertTrue(self.acme0002.has_price_breaks)
self.assertTrue(self.zergm312.has_price_breaks)
@@ -81,8 +73,7 @@ class CompanySimpleTest(TestCase):
self.assertEqual(self.zergm312.price_breaks.count(), 2)
def test_quantity_pricing(self):
""" Simple test for quantity pricing """
"""Simple test for quantity pricing."""
p = self.acme0001.get_price
self.assertEqual(p(1), 10)
self.assertEqual(p(4), 40)
@@ -99,6 +90,7 @@ class CompanySimpleTest(TestCase):
self.assertEqual(p(55), 68.75)
def test_part_pricing(self):
"""Unit tests for supplier part pricing"""
m2x4 = Part.objects.get(name='M2x4 LPHS')
self.assertEqual(m2x4.get_price_info(5.5), "38.5 - 41.25")
@@ -116,10 +108,7 @@ class CompanySimpleTest(TestCase):
self.assertIsNotNone(m3x12.get_price_info(50))
def test_currency_validation(self):
"""
Test validation for currency selection
"""
"""Test validation for currency selection."""
# Create a company with a valid currency code (should pass)
company = Company.objects.create(
name='Test',
@@ -141,8 +130,10 @@ class CompanySimpleTest(TestCase):
class ContactSimpleTest(TestCase):
"""Unit tests for the Contact model"""
def setUp(self):
"""Initialization for the tests in this class"""
# Create a simple company
self.c = Company.objects.create(name='Test Corp.', description='We make stuff good')
@@ -152,15 +143,18 @@ class ContactSimpleTest(TestCase):
Contact.objects.create(name='Sally Smith', company=self.c)
def test_exists(self):
"""Test that contacts exist"""
self.assertEqual(Contact.objects.count(), 3)
def test_delete(self):
"""Test deletion of a Contact instance"""
# Remove the parent company
Company.objects.get(pk=self.c.pk).delete()
self.assertEqual(Contact.objects.count(), 0)
class ManufacturerPartSimpleTest(TestCase):
"""Unit tests for the ManufacturerPart model"""
fixtures = [
'category',
@@ -171,6 +165,8 @@ class ManufacturerPartSimpleTest(TestCase):
]
def setUp(self):
"""Initialization for the unit tests in this class"""
# Create a manufacturer part
self.part = Part.objects.get(pk=1)
manufacturer = Company.objects.get(pk=1)
@@ -193,6 +189,7 @@ class ManufacturerPartSimpleTest(TestCase):
supplier_part.save()
def test_exists(self):
"""That that a ManufacturerPart has been created"""
self.assertEqual(ManufacturerPart.objects.count(), 4)
# Check that manufacturer part was created from supplier part creation
@@ -200,7 +197,7 @@ class ManufacturerPartSimpleTest(TestCase):
self.assertEqual(manufacturer_parts.count(), 1)
def test_delete(self):
# Remove a part
"""Test deletion of a ManufacturerPart"""
Part.objects.get(pk=self.part.id).delete()
# Check that ManufacturerPart was deleted
self.assertEqual(ManufacturerPart.objects.count(), 3)

View File

@@ -1,6 +1,4 @@
"""
URL lookup for Company app
"""
"""URL lookup for Company app."""
from django.urls import include, re_path

View File

@@ -1,6 +1,4 @@
"""
Django views for interacting with Company app
"""
"""Django views for interacting with Company app."""
import io
@@ -20,8 +18,7 @@ from .models import Company, ManufacturerPart, SupplierPart
class CompanyIndex(InvenTreeRoleMixin, ListView):
""" View for displaying list of companies
"""
"""View for displaying list of companies."""
model = Company
template_name = 'company/index.html'
@@ -30,6 +27,7 @@ class CompanyIndex(InvenTreeRoleMixin, ListView):
permission_required = 'company.view_company'
def get_context_data(self, **kwargs):
"""Add extra context data to the company index page"""
ctx = super().get_context_data(**kwargs)
@@ -80,7 +78,7 @@ class CompanyIndex(InvenTreeRoleMixin, ListView):
return ctx
def get_queryset(self):
""" Retrieve the Company queryset based on HTTP request parameters.
"""Retrieve the Company queryset based on HTTP request parameters.
- supplier: Filter by supplier
- customer: Filter by customer
@@ -97,23 +95,16 @@ class CompanyIndex(InvenTreeRoleMixin, ListView):
class CompanyDetail(InvenTreePluginViewMixin, DetailView):
""" Detail view for Company object """
"""Detail view for Company object."""
context_obect_name = 'company'
template_name = 'company/detail.html'
queryset = Company.objects.all()
model = Company
permission_required = 'company.view_company'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
return ctx
class CompanyImageDownloadFromURL(AjaxUpdateView):
"""
View for downloading an image from a provided URL
"""
"""View for downloading an image from a provided URL."""
model = Company
ajax_template_name = 'image_download.html'
@@ -121,9 +112,7 @@ class CompanyImageDownloadFromURL(AjaxUpdateView):
ajax_form_title = _('Download Image')
def validate(self, company, form):
"""
Validate that the image data are correct
"""
"""Validate that the image data are correct."""
# First ensure that the normal validation routines pass
if not form.is_valid():
return
@@ -167,9 +156,7 @@ class CompanyImageDownloadFromURL(AjaxUpdateView):
return
def save(self, company, form, **kwargs):
"""
Save the downloaded image to the company
"""
"""Save the downloaded image to the company."""
fmt = self.image.format
if not fmt:
@@ -189,28 +176,18 @@ class CompanyImageDownloadFromURL(AjaxUpdateView):
class ManufacturerPartDetail(InvenTreePluginViewMixin, DetailView):
""" Detail view for ManufacturerPart """
"""Detail view for ManufacturerPart."""
model = ManufacturerPart
template_name = 'company/manufacturer_part_detail.html'
context_object_name = 'part'
queryset = ManufacturerPart.objects.all()
permission_required = 'purchase_order.view'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
return ctx
class SupplierPartDetail(InvenTreePluginViewMixin, DetailView):
""" Detail view for SupplierPart """
"""Detail view for SupplierPart."""
model = SupplierPart
template_name = 'company/supplier_part_detail.html'
context_object_name = 'part'
queryset = SupplierPart.objects.all()
permission_required = 'purchase_order.view'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
return ctx