Skip to content

Commit

Permalink
Add add_link function to Attachment class (#214)
Browse files Browse the repository at this point in the history
* Add add_link function to Attachment class

* Fix invoke test command in TESTING.md

* Add generic AttachmentMixin class generator

* Fix ManufacturerPart attachments

* Fix multiple inheritance formatting

* Bump version to 0.13.2
  • Loading branch information
30350n authored Dec 11, 2023
1 parent b7f4cab commit da29478
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 169 deletions.
2 changes: 1 addition & 1 deletion TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Before the first test, run the following:
invoke update-image
```

The `invoke-test` command performs the following sequence of actions:
The `invoke test` command performs the following sequence of actions:

- Ensures the test InvenTree server is running (in a docker container)
- Resets the test database to a known state
Expand Down
82 changes: 75 additions & 7 deletions inventree/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import json
import logging
import os
from typing import Type

from . import api as inventree_api

INVENTREE_PYTHON_VERSION = "0.13.1"
INVENTREE_PYTHON_VERSION = "0.13.2"


logger = logging.getLogger('inventree')
Expand Down Expand Up @@ -375,8 +376,34 @@ class Attachment(BulkDeleteMixin, InventreeObject):
Multiple sub-classes exist, representing various types of attachment models in the database.
"""

# List of required kwargs required for the particular subclass
REQUIRED_KWARGS = []
# Name of the primary key field of the InventreeObject the attachment will be attached to
ATTACH_TO = None

@classmethod
def add_link(cls, api, link, comment="", **kwargs):
"""
Add an external link attachment.
Args:
api: Authenticated InvenTree API instance
link: External link to attach
comment: Add comment to the attachment
kwargs: Additional kwargs to suppl
"""

data = kwargs
data["comment"] = comment
data["link"] = link

if cls.ATTACH_TO not in kwargs:
raise ValueError(f"Required argument '{cls.ATTACH_TO}' not supplied to add_link method")

if response := api.post(cls.URL, data):
logger.info(f"Link attachment added to {cls.URL}")
else:
logger.error(f"Link attachment failed at {cls.URL}")

return response

@classmethod
def upload(cls, api, attachment, comment='', **kwargs):
Expand All @@ -394,10 +421,8 @@ def upload(cls, api, attachment, comment='', **kwargs):
data = kwargs
data['comment'] = comment

# Check that the extra kwargs are provided
for arg in cls.REQUIRED_KWARGS:
if arg not in kwargs:
raise ValueError(f"Required argument '{arg}' not supplied to upload method")
if cls.ATTACH_TO not in kwargs:
raise ValueError(f"Required argument '{cls.ATTACH_TO}' not supplied to upload method")

if type(attachment) is str:
if not os.path.exists(attachment):
Expand Down Expand Up @@ -440,6 +465,49 @@ def download(self, destination, **kwargs):
return self._api.downloadFile(self.attachment, destination, **kwargs)


def AttachmentMixin(AttachmentSubClass: Type[Attachment]):
class Mixin(Attachment):
def getAttachments(self):
return AttachmentSubClass.list(
self._api,
**{AttachmentSubClass.ATTACH_TO: self.pk},
)

def uploadAttachment(self, attachment, comment=""):
"""
Upload an attachment (file) against this Object.
Args:
attachment: Either a string (filename) or a file object
comment: Attachment comment
"""

return AttachmentSubClass.upload(
self._api,
attachment,
comment=comment,
**{AttachmentSubClass.ATTACH_TO: self.pk},
)

def addLinkAttachment(self, link, comment=""):
"""
Add an external link attachment against this Object.
Args:
link: The link to attach
comment: Attachment comment
"""

return AttachmentSubClass.add_link(
self._api,
link,
comment=comment,
**{AttachmentSubClass.ATTACH_TO: self.pk},
)

return Mixin


class MetadataMixin:
"""Mixin class for models which support a 'metadata' attribute.
Expand Down
28 changes: 9 additions & 19 deletions inventree/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@
import inventree.report


class BuildAttachment(inventree.base.Attachment):
"""Class representing an attachment against a Build object"""

URL = 'build/attachment'
ATTACH_TO = 'build'


class Build(
inventree.base.InventreeObject,
inventree.base.AttachmentMixin(BuildAttachment),
inventree.base.StatusMixin,
inventree.base.MetadataMixin,
inventree.report.ReportPrintingMixin,
inventree.base.InventreeObject,
):
""" Class representing the Build database model """

Expand All @@ -18,17 +26,6 @@ class Build(
REPORTNAME = 'build'
REPORTITEM = 'build'

def getAttachments(self):
return BuildAttachment.list(self._api, build=self.pk)

def uploadAttachment(self, attachment, comment=''):
return BuildAttachment.upload(
self._api,
attachment,
comment=comment,
build=self.pk
)

def complete(
self,
accept_overallocated='reject',
Expand All @@ -52,10 +49,3 @@ def complete(
def finish(self, *args, **kwargs):
"""Alias for complete"""
return self.complete(*args, **kwargs)


class BuildAttachment(inventree.base.Attachment):
"""Class representing an attachment against a Build object"""

URL = 'build/attachment'
REQUIRED_KWARGS = ['build']
35 changes: 13 additions & 22 deletions inventree/company.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,19 @@ def getPriceBreaks(self):
return SupplierPriceBreak.list(self._api, part=self.pk)


class ManufacturerPart(inventree.base.BulkDeleteMixin, inventree.base.MetadataMixin, inventree.base.InventreeObject):
class ManufacturerPartAttachment(inventree.base.Attachment):
"""Class representing an attachment against a ManufacturerPart object"""

URL = 'company/part/manufacturer/attachment'
ATTACH_TO = 'manufacturer_part'


class ManufacturerPart(
inventree.base.AttachmentMixin(ManufacturerPartAttachment),
inventree.base.BulkDeleteMixin,
inventree.base.MetadataMixin,
inventree.base.InventreeObject,
):
"""Class representing the ManufacturerPart database model
- Implements the BulkDeleteMixin
Expand All @@ -113,19 +125,6 @@ def getParameters(self, **kwargs):

return ManufacturerPartParameter.list(self._api, manufacturer_part=self.pk, **kwargs)

def getAttachments(self, **kwargs):

return ManufacturerPartAttachment.list(self._api, manufacturer_part=self.pk, **kwargs)

def uploadAttachment(self, attachment, comment=''):

return ManufacturerPartAttachment.upload(
self._api,
attachment,
comment=comment,
manufacturer_part=self.pk,
)


class ManufacturerPartParameter(inventree.base.BulkDeleteMixin, inventree.base.InventreeObject):
"""Class representing the ManufacturerPartParameter database model.
Expand All @@ -136,14 +135,6 @@ class ManufacturerPartParameter(inventree.base.BulkDeleteMixin, inventree.base.I
URL = 'company/part/manufacturer/parameter'


class ManufacturerPartAttachment(inventree.base.Attachment):
"""
Class representing the ManufacturerPartAttachment model
"""

URL = 'company/part/manufacturer/attachment'


class SupplierPriceBreak(inventree.base.InventreeObject):
""" Class representing the SupplierPriceBreak database model """

Expand Down
43 changes: 15 additions & 28 deletions inventree/part.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,21 @@ def getCategoryParameterTemplates(self, fetch_parent: bool = True) -> list:
)


class Part(inventree.base.BarcodeMixin, inventree.base.MetadataMixin, inventree.base.ImageMixin, inventree.label.LabelPrintingMixin, inventree.base.InventreeObject):
class PartAttachment(inventree.base.Attachment):
"""Class representing a file attachment for a Part"""

URL = 'part/attachment'
ATTACH_TO = 'part'


class Part(
inventree.base.AttachmentMixin(PartAttachment),
inventree.base.BarcodeMixin,
inventree.base.MetadataMixin,
inventree.base.ImageMixin,
inventree.label.LabelPrintingMixin,
inventree.base.InventreeObject,
):
""" Class representing the Part database model """

URL = 'part'
Expand Down Expand Up @@ -124,25 +138,6 @@ def setInternalPrice(self, quantity: int, price: float):

return InternalPrice.setInternalPrice(self._api, self.pk, quantity, price)

def getAttachments(self):
return PartAttachment.list(self._api, part=self.pk)

def uploadAttachment(self, attachment, comment=''):
"""
Upload an attachment (file) against this Part.
Args:
attachment: Either a string (filename) or a file object
comment: Attachment comment
"""

return PartAttachment.upload(
self._api,
attachment,
comment=comment,
part=self.pk
)

def getRequirements(self):
"""
Get required amounts from requirements API endpoint for this part
Expand All @@ -155,14 +150,6 @@ def getRequirements(self):
return self._api.get(URL)


class PartAttachment(inventree.base.Attachment):
""" Class representing a file attachment for a Part """

URL = 'part/attachment'

REQUIRED_KWARGS = ['part']


class PartTestTemplate(inventree.base.MetadataMixin, inventree.base.InventreeObject):
""" Class representing a test template for a Part """

Expand Down
28 changes: 9 additions & 19 deletions inventree/purchase_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@
import inventree.report


class PurchaseOrderAttachment(inventree.base.Attachment):
"""Class representing a file attachment for a PurchaseOrder"""

URL = 'order/po/attachment'
ATTACH_TO = 'order'


class PurchaseOrder(
inventree.base.AttachmentMixin(PurchaseOrderAttachment),
inventree.base.MetadataMixin,
inventree.base.InventreeObject,
inventree.base.StatusMixin,
inventree.report.ReportPrintingMixin,
inventree.base.InventreeObject,
):
""" Class representing the PurchaseOrder database model """

Expand Down Expand Up @@ -59,17 +67,6 @@ def addExtraLineItem(self, **kwargs):

return PurchaseOrderExtraLineItem.create(self._api, data=kwargs)

def getAttachments(self):
return PurchaseOrderAttachment.list(self._api, order=self.pk)

def uploadAttachment(self, attachment, comment=''):
return PurchaseOrderAttachment.upload(
self._api,
attachment,
comment=comment,
order=self.pk,
)

def issue(self, **kwargs):
"""
Issue the purchase order
Expand Down Expand Up @@ -248,10 +245,3 @@ def getOrder(self):
Return the PurchaseOrder to which this PurchaseOrderLineItem belongs
"""
return PurchaseOrder(self._api, self.order)


class PurchaseOrderAttachment(inventree.base.Attachment):
"""Class representing a file attachment for a PurchaseOrder"""

URL = 'order/po/attachment'
REQUIRED_KWARGS = ['order']
32 changes: 10 additions & 22 deletions inventree/return_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@
import inventree.stock


class ReturnOrderAttachment(inventree.base.InventreeObject):
"""Class representing the ReturnOrderAttachment model"""

URL = 'order/ro/attachment'
ATTACH_TO = 'order'
REQUIRED_API_VERSION = 104


class ReturnOrder(
inventree.base.AttachmentMixin(ReturnOrderAttachment),
inventree.base.MetadataMixin,
inventree.base.InventreeObject,
inventree.base.StatusMixin,
inventree.report.ReportPrintingMixin,
inventree.base.InventreeObject,
):
"""Class representing the ReturnOrder database model"""

Expand Down Expand Up @@ -53,19 +62,6 @@ def addExtraLineItem(self, **kwargs):
kwargs['order'] = self.pk
return ReturnOrderExtraLineItem.create(self._api, data=kwargs)

def getAttachments(self):
"""Return a list of attachments associated with this order"""
return ReturnOrderAttachment.list(self._api, order=self.pk)

def uploadAttachment(self, attachment, comment=''):
"""Upload a file attachment against this order"""
return ReturnOrderAttachment.upload(
self._api,
attachment,
comment=comment,
order=self.pk
)

def issue(self, **kwargs):
"""Issue (send) this order"""
return self._statusupdate(status='issue', **kwargs)
Expand Down Expand Up @@ -103,11 +99,3 @@ class ReturnOrderExtraLineItem(inventree.base.InventreeObject):
def getOrder(self):
"""Return the ReturnOrder to which this line item belongs"""
return ReturnOrder(self._api, self.order)


class ReturnOrderAttachment(inventree.base.InventreeObject):
"""Class representing the ReturnOrderAttachment model"""

URL = 'order/ro/attachment'
REQUIRED_KWARGS = ['order']
REQUIRED_API_VERSION = 104
Loading

0 comments on commit da29478

Please sign in to comment.