I have a module PartLog witch is used to generate a report from another modules like stock, part and user. I am also using message to store in report description fileds :- look
My PartLog Model:-
class PartLog(models.Model):
ACTION_ADD = 'Added Part'
ACTION_REMOVED = 'Removed Part'
ACTION_DUPLICATED = 'Part Duplicated'
ACTION_EDITED = 'Part Updated'
ACTION_ADD_ORDER_TO_PART = 'Added Order to Part'
ACTION_ADD_STOCK_TO_PART = 'Added %s items to stock'
ACTION_REMOVE_FROM_STOCK = 'Removed %s items from stock'
ACTION_CREATED_STOCK = 'Created Stock Item'
ACTION_DELETED_STOCK = 'Removed Stock Item'
ACTION_MOVED_TO = 'Stock item moved'
ACTION_STOCK_EDITED = 'Stock Updated'
ACTION_STOCK_DUPLICATED = 'Stock Duplicated'
ACTION_ORDERED_PART = 'Part Ordered'
ACTION_CHOICES = (
(ACTION_ADD, ACTION_ADD),
(ACTION_REMOVED, ACTION_REMOVED),
(ACTION_DUPLICATED, ACTION_DUPLICATED),
(ACTION_EDITED, ACTION_EDITED),
(ACTION_ADD_ORDER_TO_PART, ACTION_ADD_ORDER_TO_PART),
(ACTION_ADD_STOCK_TO_PART, ACTION_ADD_STOCK_TO_PART),
(ACTION_REMOVE_FROM_STOCK, ACTION_REMOVE_FROM_STOCK),
(ACTION_CREATED_STOCK, ACTION_CREATED_STOCK),
(ACTION_DELETED_STOCK, ACTION_DELETED_STOCK),
(ACTION_MOVED_TO, ACTION_MOVED_TO),
(ACTION_STOCK_EDITED, ACTION_STOCK_EDITED),
(ACTION_STOCK_DUPLICATED, ACTION_STOCK_DUPLICATED),
(ACTION_ORDERED_PART, ACTION_ORDERED_PART),
)
part = models.ForeignKey(to=Part, on_delete=models.SET_NULL, null=True)
transaction_time = models.DateTimeField('Transaction Time')
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
change_quantity = models.PositiveSmallIntegerField('Change Quantity')
action = models.CharField(max_length=40, choices=ACTION_CHOICES)
@property
def message(self):
if self.action in [self.ACTION_REMOVE_FROM_STOCK, self.ACTION_ADD_STOCK_TO_PART]:
return self.action % self.change_quantity
return self.action
@staticmethod
def log(part, user, change_quantity, action):
PartLog.objects.create(part=part, user=user,
change_quantity=change_quantity,
action=action, transaction_time=datetime.now())
My Stock Item Create view:-
class StockItemCreate(PermissionRequiredMixin, AjaxCreateView):
"""
View for creating a new StockItem
Parameters can be pre-filled by passing query items:
- part: The part of which the new StockItem is an instance
- location: The location of the new StockItem
If the parent part is a "tracked" part, provide an option to create uniquely serialized items
rather than a bulk quantity of stock items
"""
permission_required = ('users.add_stocks',)
model = StockItem
form_class = CreateStockItemForm
context_object_name = 'item'
ajax_template_name = 'modal_form.html'
ajax_form_title = _('Create new Stock Item')
def get_form(self):
""" Get form for StockItem creation.
Overrides the default get_form() method to intelligently limit
ForeignKey choices based on other selections
"""
form = super().get_form()
# If the user has selected a Part, limit choices for SupplierPart
if form['part'].value():
part_id = form['part'].value()
try:
part = Part.objects.get(id=part_id)
# Hide the 'part' field (as a valid part is selected)
form.fields['part'].widget = HiddenInput()
# trackable parts get special consideration
if part.trackable:
form.fields['delete_on_deplete'].widget = HiddenInput()
form.fields['delete_on_deplete'].initial = False
else:
form.fields.pop('serial_numbers')
# If the part is NOT purchaseable, hide the supplier_part field
if not part.purchaseable:
form.fields['supplier_part'].widget = HiddenInput()
else:
# Pre-select the allowable SupplierPart options
parts = form.fields['supplier_part'].queryset
parts = parts.filter(part=part.id)
form.fields['supplier_part'].queryset = parts
# If there is one (and only one) supplier part available, pre-select it
all_parts = parts.all()
if len(all_parts) == 1:
# TODO - This does NOT work for some reason? Ref build.views.BuildItemCreate
form.fields['supplier_part'].initial = all_parts[0].id
except Part.DoesNotExist:
pass
# Otherwise if the user has selected a SupplierPart, we know what Part they meant!
elif form['supplier_part'].value() is not None:
pass
return form
def get_initial(self):
""" Provide initial data to create a new StockItem object
"""
# Is the client attempting to copy an existing stock item?
item_to_copy = self.request.GET.get('copy', None)
if item_to_copy:
try:
original = StockItem.objects.get(pk=item_to_copy)
initials = model_to_dict(original)
self.ajax_form_title = _("Copy Stock Item")
except StockItem.DoesNotExist:
initials = super(StockItemCreate, self).get_initial().copy()
else:
initials = super(StockItemCreate, self).get_initial().copy()
part_id = self.request.GET.get('part', None)
loc_id = self.request.GET.get('location', None)
# Part field has been specified
if part_id:
try:
part = Part.objects.get(pk=part_id)
initials['part'] = part
initials['location'] = part.get_default_location()
initials['supplier_part'] = part.default_supplier
except Part.DoesNotExist:
pass
# Location has been specified
if loc_id:
try:
initials['location'] = StockLocation.objects.get(pk=loc_id)
except StockLocation.DoesNotExist:
pass
return initials
def post(self, request, *args, **kwargs):
""" Handle POST of StockItemCreate form.
- Manage serial-number valdiation for tracked parts
"""
form = self.get_form()
data = {}
valid = form.is_valid()
if valid:
part_id = form['part'].value()
try:
part = Part.objects.get(id=part_id)
quantity = Decimal(form['quantity'].value())
except (Part.DoesNotExist, ValueError, InvalidOperation):
part = None
quantity = 1
valid = False
form.errors['quantity'] = [_('Invalid quantity')]
if part is None:
form.errors['part'] = [_('Invalid part selection')]
else:
# A trackable part must provide serial numbesr
if part.trackable:
sn = request.POST.get('serial_numbers', '')
sn = str(sn).strip()
# If user has specified a range of serial numbers
if len(sn) > 0:
try:
serials = ExtractSerialNumbers(sn, quantity)
existing = []
for serial in serials:
if not StockItem.check_serial_number(part, serial):
existing.append(serial)
if len(existing) > 0:
exists = ",".join([str(x) for x in existing])
form.errors['serial_numbers'] = [_('The following serial numbers already exist: ({sn})'.format(sn=exists))]
valid = False
# At this point we have a list of serial numbers which we know are valid,
# and do not currently exist
form.clean()
data = form.cleaned_data
for serial in serials:
# Create a new stock item for each serial number
item = StockItem(
part=part,
quantity=1,
serial=serial,
supplier_part=data.get('supplier_part'),
location=data.get('location'),
batch=data.get('batch'),
delete_on_deplete=False,
status=data.get('status'),
notes=data.get('notes'),
URL=data.get('URL'),
)
item.save(user=request.user)
except ValidationError as e:
form.errors['serial_numbers'] = e.messages
valid = False
else:
# For non-serialized items, simply save the form.
# We need to call _post_clean() here because it is prevented in the form implementation
form.clean()
form._post_clean()
item = form.save(commit=False)
item.save(user=request.user)
PartLog.log(
part=part, change_quantity=form.cleaned_data.get('quantity', 0),
action=PartLog.ACTION_ADD_STOCK_TO_PART, user=request.user
)
data['pk'] = item.pk
data['url'] = item.get_absolute_url()
data['success'] = _("Created new stock item")
data['form_valid'] = valid
return self.renderJsonResponse(request, form, data=data)
Need to show message in description like " Added %S items to stock with %S location "
Location is just we select at the time of Stock item creation.