MY MODEL:-
class BomItem(models.Model):
def get_absolute_url(self):
return reverse('bom-item-detail', kwargs={'pk': self.id})
# A link to the parent part
# Each part will get a reverse lookup field 'bom_items'
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='bom_items',
help_text=_('Select parent part'),
limit_choices_to={
'assembly': True,
'is_template': False,
})
# A link to the child item (sub-part)
# Each part will get a reverse lookup field 'used_in'
sub_part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='used_in',
help_text=_('Select part to be used in BOM'),
limit_choices_to={
'component': True,
'is_template': False,
})
# Quantity required
quantity = models.DecimalField(default=1.0, max_digits=15, decimal_places=5, validators=[MinValueValidator(0)],
help_text=_('BOM quantity for this BOM item'))
overage = models.CharField(max_length=24, blank=True, validators=[validators.validate_overage],
help_text=_('Estimated build wastage quantity (absolute or percentage)')
)
reference = models.CharField(max_length=500, blank=True, help_text=_('BOM item reference'))
# Note attached to this BOM line item
note = models.CharField(max_length=500, blank=True, help_text=_('BOM item notes'))
checksum = models.CharField(max_length=128, blank=True, help_text=_('BOM line checksum'))
def get_item_hash(self):
""" Calculate the checksum hash of this BOM line item:
The hash is calculated from the following fields:
- Part.full_name (if the part name changes, the BOM checksum is invalidated)
- Quantity
- Reference field
- Note field
"""
# Seed the hash with the ID of this BOM item
hash = hashlib.md5(str(self.id).encode())
# Update the hash based on line information
hash.update(str(self.sub_part.id).encode())
hash.update(str(self.sub_part.full_name).encode())
hash.update(str(self.quantity).encode())
hash.update(str(self.note).encode())
hash.update(str(self.reference).encode())
return str(hash.digest())
def validate_hash(self, valid=True):
""" Mark this item as 'valid' (store the checksum hash).
Args:
valid: If true, validate the hash, otherwise invalidate it (default = True)
"""
if valid:
self.checksum = str(self.get_item_hash())
else:
self.checksum = ''
self.save()
@property
def is_line_valid(self):
""" Check if this line item has been validated by the user """
# Ensure an empty checksum returns False
if len(self.checksum) == 0:
return False
return self.get_item_hash() == self.checksum
def clean(self):
""" Check validity of the BomItem model.
Performs model checks beyond simple field validation.
- A part cannot refer to itself in its BOM
- A part cannot refer to a part which refers to it
"""
# A part cannot refer to itself in its BOM
try:
if self.sub_part is not None and self.part is not None:
if self.part == self.sub_part:
raise ValidationError({'sub_part': _('Part cannot be added to its own Bill of Materials')})
# TODO - Make sure that there is no recusion
# Test for simple recursion
for item in self.sub_part.bom_items.all():
if self.part == item.sub_part:
raise ValidationError({'sub_part': _(
"Part '{p1}' is used in BOM for '{p2}' (recursive)".format(p1=str(self.part),
p2=str(self.sub_part)))})
except Part.DoesNotExist:
# A blank Part will be caught elsewhere
pass
class Meta:
verbose_name = "BOM Item"
# Prevent duplication of parent/child rows
unique_together = ('part', 'sub_part')
def __str__(self):
return "{n} x {child} to make {parent}".format(
parent=self.part.full_name,
child=self.sub_part.full_name,
n=helpers.decimal2string(self.quantity))
def get_overage_quantity(self, quantity):
""" Calculate overage quantity
"""
# Most of the time overage string will be empty
if len(self.overage) == 0:
return 0
overage = str(self.overage).strip()
# Is the overage a numerical value?
try:
ovg = float(overage)
if ovg < 0:
ovg = 0
return ovg
except ValueError:
pass
# Is the overage a percentage?
if overage.endswith('%'):
overage = overage[:-1].strip()
try:
percent = float(overage) / 100.0
if percent > 1:
percent = 1
if percent < 0:
percent = 0
# Must be represented as a decimal
percent = Decimal(percent)
return float(percent * quantity)
except ValueError:
pass
# Default = No overage
return 0
def get_required_quantity(self, build_quantity):
""" Calculate the required part quantity, based on the supplier build_quantity.
Includes overage estimate in the returned value.
Args:
build_quantity: Number of parts to build
Returns:
Quantity required for this build (including overage)
"""
# Base quantity requirement
base_quantity = self.quantity * build_quantity
# Overage requiremet
ovrg_quantity = self.get_overage_quantity(base_quantity)
required = float(base_quantity) + float(ovrg_quantity)
return required
@property
def price_range(self):
""" Return the price-range for this BOM item. """
prange = self.sub_part.get_price_range(self.quantity)
if prange is None:
return prange
pmin, pmax = prange
if pmin == pmax:
return decimal2string(pmin)
# Convert to better string representation
pmin = decimal2string(pmin)
pmax = decimal2string(pmax)
return "{pmin} to {pmax}".format(pmin=pmin, pmax=pmax)
My admin.py from where I am exporting my data:-
class BomItemResource(ModelResource):
""" Class for managing BomItem data import/export """
sub_part_name = Field(attribute='sub_part__full_name', readonly=True)
sub_part_description = Field(attribute='sub_part__description', readonly=True)
available = Field(attribute='sub_part__total_stock', readonly=True)
price_range = Field(attribute='sub_part', readonly=True) #I want here my price how can I do this.
class Meta:
model = BomItem
skip_unchanged = True
report_skipped = False
clean_model_instances = True
exclude = [
'checksum',
'id',
'part',
'sub_part',
'overage',
]