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 merge99676ee
* 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 commitca96654622
. * use typing here * Revert "Make API code cleaner" This reverts commit24fb68bd3e
. * 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:
@@ -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):
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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',
|
||||
|
@@ -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}'
|
||||
|
@@ -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',
|
||||
|
@@ -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
|
||||
|
@@ -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():
|
||||
|
@@ -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')
|
||||
|
@@ -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)
|
||||
|
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
URL lookup for Company app
|
||||
"""
|
||||
"""URL lookup for Company app."""
|
||||
|
||||
from django.urls import include, re_path
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user