Skip to content

Commit db1226e

Browse files
Refactor attachment class (#234)
* Refactor attachment class - Support legacy API - Support modern API * Update test process - Skip steps we don't need * Fix URL * Simplify attachments model - Min API version is now 207 - Just use a single Attachment table * Fix docker compose file for testing - Not sure why this was pinned at 0.12.8 - Latest and greatest, thanks! * Cleanup unit tests * Cleanup test_part.py * Clean containers after run * PEP style fixes * Update unit tests * Unit test updates * More unit test updates * Remove unused imports * Unit test updates * Debug statements * Fix test call * Tweak unit tests - Skip attachment tests for older API version * Bump version number - Skip to 0.16.0 - Lines up with supported server version * Skip test
1 parent 4ba08f5 commit db1226e

22 files changed

+136
-519
lines changed

inventree/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class InvenTreeAPI(object):
2222
Basic class for performing Inventree API requests.
2323
"""
2424

25-
MIN_SUPPORTED_API_VERSION = 51
25+
MIN_SUPPORTED_API_VERSION = 206
2626

2727
@staticmethod
2828
def getMinApiVersion():

inventree/base.py

Lines changed: 49 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
import json
44
import logging
55
import os
6-
from typing import Type
76

87
from . import api as inventree_api
98

10-
INVENTREE_PYTHON_VERSION = "0.14.0"
9+
INVENTREE_PYTHON_VERSION = "0.16.0"
1110

1211

1312
logger = logging.getLogger('inventree')
@@ -19,6 +18,11 @@ class InventreeObject(object):
1918
# API URL (required) for the particular model type
2019
URL = ""
2120

21+
@classmethod
22+
def get_url(cls, api):
23+
"""Helper method to get the URL associated with this model."""
24+
return cls.URL
25+
2226
# Minimum server version for the particular model type
2327
MIN_API_VERSION = None
2428
MAX_API_VERSION = None
@@ -85,7 +89,9 @@ def __init__(self, api, pk=None, data=None):
8589
if pk <= 0:
8690
raise ValueError(f"Supplier <pk> value ({pk}) for {self.__class__} must be positive.")
8791

88-
self._url = f"{self.URL}/{pk}/"
92+
url = self.get_url(api)
93+
94+
self._url = f"{url}/{pk}/"
8995
self._api = api
9096

9197
if data is None:
@@ -374,9 +380,6 @@ def bulkDelete(cls, api: inventree_api.InvenTreeAPI, items=None, filters=None):
374380
375381
"""
376382

377-
if api.api_version < 58:
378-
raise NotImplementedError("bulkDelete requires API version 58 or newer")
379-
380383
if not items and not filters:
381384
raise ValueError("Must supply either 'items' or 'filters' argument")
382385

@@ -395,14 +398,12 @@ def bulkDelete(cls, api: inventree_api.InvenTreeAPI, items=None, filters=None):
395398

396399

397400
class Attachment(BulkDeleteMixin, InventreeObject):
398-
"""
399-
Class representing a file attachment object
401+
"""Class representing a file attachment object."""
400402

401-
Multiple sub-classes exist, representing various types of attachment models in the database.
402-
"""
403+
URL = 'attachment/'
403404

404-
# Name of the primary key field of the InventreeObject the attachment will be attached to
405-
ATTACH_TO = None
405+
# Ref: https://github.com/inventree/InvenTree/pull/7420
406+
MIN_API_VERSION = 207
406407

407408
@classmethod
408409
def add_link(cls, api, link, comment="", **kwargs):
@@ -420,9 +421,6 @@ def add_link(cls, api, link, comment="", **kwargs):
420421
data["comment"] = comment
421422
data["link"] = link
422423

423-
if cls.ATTACH_TO not in kwargs:
424-
raise ValueError(f"Required argument '{cls.ATTACH_TO}' not supplied to add_link method")
425-
426424
if response := api.post(cls.URL, data):
427425
logger.info(f"Link attachment added to {cls.URL}")
428426
else:
@@ -446,18 +444,14 @@ def upload(cls, api, attachment, comment='', **kwargs):
446444
data = kwargs
447445
data['comment'] = comment
448446

449-
if cls.ATTACH_TO not in kwargs:
450-
raise ValueError(f"Required argument '{cls.ATTACH_TO}' not supplied to upload method")
451-
452447
if type(attachment) is str:
453448
if not os.path.exists(attachment):
454449
raise FileNotFoundError(f"Attachment file '{attachment}' does not exist")
455450

456451
# Load the file as an in-memory file object
457452
with open(attachment, 'rb') as fo:
458-
459453
response = api.post(
460-
cls.URL,
454+
Attachment.URL,
461455
data,
462456
files={
463457
'attachment': (os.path.basename(attachment), fo),
@@ -467,8 +461,9 @@ def upload(cls, api, attachment, comment='', **kwargs):
467461
else:
468462
# Assumes a StringIO or BytesIO like object
469463
name = getattr(attachment, 'name', 'filename')
464+
470465
response = api.post(
471-
cls.URL,
466+
Attachment.URL,
472467
data,
473468
files={
474469
'attachment': (name, attachment),
@@ -490,47 +485,44 @@ def download(self, destination, **kwargs):
490485
return self._api.downloadFile(self.attachment, destination, **kwargs)
491486

492487

493-
def AttachmentMixin(AttachmentSubClass: Type[Attachment]):
494-
class Mixin(Attachment):
495-
def getAttachments(self):
496-
return AttachmentSubClass.list(
497-
self._api,
498-
**{AttachmentSubClass.ATTACH_TO: self.pk},
499-
)
488+
class AttachmentMixin:
489+
"""Mixin class which allows a model class to interact with attachments."""
500490

501-
def uploadAttachment(self, attachment, comment=""):
502-
"""
503-
Upload an attachment (file) against this Object.
491+
def getAttachments(self):
492+
"""Return a list of attachments associated with this object."""
504493

505-
Args:
506-
attachment: Either a string (filename) or a file object
507-
comment: Attachment comment
508-
"""
494+
return Attachment.list(
495+
self._api,
496+
model_type=self.getModelType(),
497+
model_id=self.pk
498+
)
509499

510-
return AttachmentSubClass.upload(
511-
self._api,
512-
attachment,
513-
comment=comment,
514-
**{AttachmentSubClass.ATTACH_TO: self.pk},
515-
)
500+
def uploadAttachment(self, attachment, comment=""):
501+
"""Upload a file attachment against this model instance."""
502+
503+
return Attachment.upload(
504+
self._api,
505+
attachment,
506+
comment=comment,
507+
model_type=self.getModelType(),
508+
model_id=self.pk
509+
)
516510

517-
def addLinkAttachment(self, link, comment=""):
518-
"""
519-
Add an external link attachment against this Object.
520-
521-
Args:
522-
link: The link to attach
523-
comment: Attachment comment
524-
"""
525-
526-
return AttachmentSubClass.add_link(
527-
self._api,
528-
link,
529-
comment=comment,
530-
**{AttachmentSubClass.ATTACH_TO: self.pk},
531-
)
511+
def addLinkAttachment(self, link, comment=""):
512+
"""Add an external link attachment against this Object.
532513
533-
return Mixin
514+
Args:
515+
link: The link to attach
516+
comment: Attachment comment
517+
"""
518+
519+
return Attachment.add_link(
520+
self._api,
521+
link,
522+
comment=comment,
523+
model_type=self.getModelType(),
524+
model_id=self.pk
525+
)
534526

535527

536528
class MetadataMixin:
@@ -553,10 +545,6 @@ def getMetadata(self):
553545
"""Read model instance metadata"""
554546
if self._api:
555547

556-
if self._api.api_version < 49:
557-
logger.error("API version 49 or newer required to access instance metadata")
558-
return {}
559-
560548
response = self._api.get(self.metadata_url)
561549

562550
return response['metadata']
@@ -576,10 +564,6 @@ def setMetadata(self, data, overwrite=False):
576564

577565
if self._api:
578566

579-
if self._api.api_version < 49:
580-
logger.error("API version 49 or newer required to access instance metadata")
581-
return None
582-
583567
if overwrite:
584568
return self._api.put(
585569
self.metadata_url,

inventree/build.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,8 @@
44
import inventree.report
55

66

7-
class BuildAttachment(inventree.base.Attachment):
8-
"""Class representing an attachment against a Build object"""
9-
10-
URL = 'build/attachment'
11-
ATTACH_TO = 'build'
12-
13-
147
class Build(
15-
inventree.base.AttachmentMixin(BuildAttachment),
8+
inventree.base.AttachmentMixin,
169
inventree.base.StatusMixin,
1710
inventree.base.MetadataMixin,
1811
inventree.report.ReportPrintingMixin,
@@ -23,10 +16,6 @@ class Build(
2316
URL = 'build'
2417
MODEL_TYPE = 'build'
2518

26-
# Setup for Report mixin
27-
REPORTNAME = 'build'
28-
REPORTITEM = 'build'
29-
3019
def complete(
3120
self,
3221
accept_overallocated='reject',

inventree/company.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Company(inventree.base.ImageMixin, inventree.base.MetadataMixin, inventree
2626
""" Class representing the Company database model """
2727

2828
URL = 'company'
29+
MODEL_TYPE = "company"
2930

3031
def getContacts(self, **kwargs):
3132
"""Return contacts associated with this Company"""
@@ -110,15 +111,8 @@ def getPriceBreaks(self):
110111
return SupplierPriceBreak.list(self._api, part=self.pk)
111112

112113

113-
class ManufacturerPartAttachment(inventree.base.Attachment):
114-
"""Class representing an attachment against a ManufacturerPart object"""
115-
116-
URL = 'company/part/manufacturer/attachment'
117-
ATTACH_TO = 'manufacturer_part'
118-
119-
120114
class ManufacturerPart(
121-
inventree.base.AttachmentMixin(ManufacturerPartAttachment),
115+
inventree.base.AttachmentMixin,
122116
inventree.base.BulkDeleteMixin,
123117
inventree.base.MetadataMixin,
124118
inventree.base.InventreeObject,
@@ -129,6 +123,7 @@ class ManufacturerPart(
129123
"""
130124

131125
URL = 'company/part/manufacturer'
126+
MODEL_TYPE = "manufacturerpart"
132127

133128
def getParameters(self, **kwargs):
134129
"""

inventree/currency.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,11 @@ def __init__(self, api):
2323
def refreshExchangeRates(self):
2424
"""Request the server update exchange rates from external service"""
2525

26-
if self.api.api_version < 93:
27-
raise ValueError(f"Server API version ({self.api.api_version}) is older than v93, which is required for manual exchange rate updates")
28-
2926
return self.api.post('currency/refresh/', {})
3027

3128
def updateFromServer(self):
3229
"""Retrieve currency data from the server"""
3330

34-
if self.api.api_version < 92:
35-
raise ValueError(f"Server API version ({self.api.api_version}) is older than v92, which is required for currency support")
36-
3731
response = self.api.get(self.CURRENCY_ENDPOINT)
3832

3933
if response is None:

0 commit comments

Comments
 (0)