From a81f9846ace1de389926ee8076a6a57e72712416 Mon Sep 17 00:00:00 2001 From: Brandon King Date: Sat, 22 Aug 2009 00:08:55 +0000 Subject: [PATCH] Implemented custom templates by inventory type using PrinterTemplate object. * bcmagic app now has a Printer object which stores useable printers * PrinterTemplate links Item Type, Printer, and Template together. * Templates now stored in db in PrinterTemplate object. --- htsworkflow/frontend/bcmagic/admin.py | 8 +- .../bcmagic/fixtures/initial_data.json | 2 +- htsworkflow/frontend/bcmagic/models.py | 22 +++++ htsworkflow/frontend/inventory/admin.py | 6 +- .../inventory/fixtures/initial_data.json | 1 + htsworkflow/frontend/inventory/models.py | 47 +++++++++- htsworkflow/frontend/inventory/views.py | 90 ++++++++++++++++--- .../templates/inventory/hard_drive_shell.zpl | 26 +++--- 8 files changed, 171 insertions(+), 31 deletions(-) create mode 100644 htsworkflow/frontend/inventory/fixtures/initial_data.json diff --git a/htsworkflow/frontend/bcmagic/admin.py b/htsworkflow/frontend/bcmagic/admin.py index 2dce2ec..073c3e8 100644 --- a/htsworkflow/frontend/bcmagic/admin.py +++ b/htsworkflow/frontend/bcmagic/admin.py @@ -1,7 +1,11 @@ from django.contrib import admin -from htsworkflow.frontend.bcmagic.models import KeywordMap +from htsworkflow.frontend.bcmagic.models import KeywordMap, Printer class KeywordMapAdmin(admin.ModelAdmin): list_display = ('keyword','regex', 'url_template') -admin.site.register(KeywordMap, KeywordMapAdmin) \ No newline at end of file +class PrinterAdmin(admin.ModelAdmin): + list_display = ('name', 'model', 'ip_address', 'label_shape', 'label_width', 'label_height', 'notes') + +admin.site.register(KeywordMap, KeywordMapAdmin) +admin.site.register(Printer, PrinterAdmin) \ No newline at end of file diff --git a/htsworkflow/frontend/bcmagic/fixtures/initial_data.json b/htsworkflow/frontend/bcmagic/fixtures/initial_data.json index 09af654..1797320 100644 --- a/htsworkflow/frontend/bcmagic/fixtures/initial_data.json +++ b/htsworkflow/frontend/bcmagic/fixtures/initial_data.json @@ -1 +1 @@ -[{"pk": 1, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P[A-Fa-f0-9]+)", "url_template": "/samples/freezer/{{ uuid }}/", "keyword": "frzr"}}, {"pk": 2, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P[A-Fa-f0-9]+)", "url_template": "/samples/container/{{ uuid }}/", "keyword": "cntr"}}, {"pk": 3, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P\\d+)\\|(?P[A-Za-z0-9_\\- ]+)", "url_template": "/samples/sample/{{ sampleid }}/", "keyword": "s"}}, {"pk": 4, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P[\\S\\s]+)", "url_template": "http://www.google.com/search?q={{ search }}", "keyword": "gg"}}, {"pk": 5, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P[\\S\\s]+)", "url_template": "http://www.flickr.com/search/?q={{ search }}", "keyword": "flickr"}}, {"pk": 6, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P[A-Fa-f0-9]+)", "url_template": "/inventory/{{ uuid }}/", "keyword": "invu"}}, {"pk": 7, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P.+)", "url_template": "/inventory/{{barcode_id}}/", "keyword": "invb"}}] +[{"pk": 1, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P[A-Fa-f0-9]+)", "url_template": "/samples/freezer/{{ uuid }}/", "keyword": "frzr"}}, {"pk": 2, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P[A-Fa-f0-9]+)", "url_template": "/samples/container/{{ uuid }}/", "keyword": "cntr"}}, {"pk": 3, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P\\d+)\\|(?P[A-Za-z0-9_\\- ]+)", "url_template": "/samples/sample/{{ sampleid }}/", "keyword": "s"}}, {"pk": 4, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P[\\S\\s]+)", "url_template": "http://www.google.com/search?q={{ search }}", "keyword": "gg"}}, {"pk": 5, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P[\\S\\s]+)", "url_template": "http://www.flickr.com/search/?q={{ search }}", "keyword": "flickr"}}, {"pk": 6, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P[A-Fa-f0-9]+)", "url_template": "/inventory/{{ uuid }}/", "keyword": "invu"}}, {"pk": 7, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P.+)", "url_template": "/inventory/{{barcode_id}}/", "keyword": "invb"}}, {"pk": 1, "model": "bcmagic.printer", "fields": {"name": "ZM400 1.25x1", "label_height": 1.0, "notes": "Everyday use labels", "label_width": 1.25, "label_shape": "Square", "model": "Zebra ZM400", "ip_address": "131.215.54.194"}}, {"pk": 2, "model": "bcmagic.printer", "fields": {"name": "ZM400 3x3", "label_height": 3.0, "notes": "Larger everyday use labels", "label_width": 3.0, "label_shape": "Square", "model": "Zebra ZM400", "ip_address": "131.215.34.199"}}] diff --git a/htsworkflow/frontend/bcmagic/models.py b/htsworkflow/frontend/bcmagic/models.py index 12d8736..4db58a3 100644 --- a/htsworkflow/frontend/bcmagic/models.py +++ b/htsworkflow/frontend/bcmagic/models.py @@ -1,5 +1,12 @@ from django.db import models +#FIXME: Should be made more generic and probably pre-populated supported list +# but for now, since we only have a ZM400, this will do. +PRINTER_MODELS=[ ('Zebra ZM400', 'Zebra ZM400'), + ('Zebra ZM600', 'Zebra ZM600')] + +LABEL_SHAPES = [ ('Square', 'Square'), ('Circle', 'Circle') ] + class KeywordMap(models.Model): """ Mapper object maps keyword|arg1|arg2|...|argN to REST urls @@ -7,3 +14,18 @@ class KeywordMap(models.Model): keyword = models.CharField(max_length=64) regex = models.CharField(max_length=1024) url_template = models.TextField() + +class Printer(models.Model): + """ + Barcode Printer Information + """ + name = models.CharField(max_length=256) + model = models.CharField(max_length=64, choices=PRINTER_MODELS) + ip_address = models.IPAddressField() + label_shape = models.CharField(max_length=32, choices=LABEL_SHAPES) + label_width = models.FloatField(help_text='width or diameter in inches') + label_height = models.FloatField(help_text='height in inches') + notes = models.TextField() + + def __unicode__(self): + return u'%s, %s, %s, %s, %sx%s' % (self.name, self.model, self.ip_address, self.label_shape, self.label_width, self.label_width) \ No newline at end of file diff --git a/htsworkflow/frontend/inventory/admin.py b/htsworkflow/frontend/inventory/admin.py index 38b3fee..7aa0fe5 100644 --- a/htsworkflow/frontend/inventory/admin.py +++ b/htsworkflow/frontend/inventory/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from htsworkflow.frontend.inventory.models import Item, ItemInfo, ItemType, Vendor, Location, LongTermStorage, ItemStatus, ReagentFlowcell, ReagentLibrary +from htsworkflow.frontend.inventory.models import Item, ItemInfo, ItemType, Vendor, Location, LongTermStorage, ItemStatus, ReagentFlowcell, ReagentLibrary, PrinterTemplate class ItemAdmin(admin.ModelAdmin): save_as = True @@ -43,6 +43,9 @@ class ReagentFlowcellAdmin(admin.ModelAdmin): class ReagentLibraryAdmin(admin.ModelAdmin): pass +class PrinterTemplateAdmin(admin.ModelAdmin): + list_display = ('item_type', 'printer', 'default') + admin.site.register(Item, ItemAdmin) admin.site.register(ItemInfo, ItemInfoAdmin) admin.site.register(ItemType, ItemTypeAdmin) @@ -52,4 +55,5 @@ admin.site.register(LongTermStorage, LongTermStorageAdmin) admin.site.register(ItemStatus, ItemStatusAdmin) admin.site.register(ReagentFlowcell, ReagentFlowcellAdmin) admin.site.register(ReagentLibrary, ReagentLibraryAdmin) +admin.site.register(PrinterTemplate, PrinterTemplateAdmin) diff --git a/htsworkflow/frontend/inventory/fixtures/initial_data.json b/htsworkflow/frontend/inventory/fixtures/initial_data.json new file mode 100644 index 0000000..ae0ae12 --- /dev/null +++ b/htsworkflow/frontend/inventory/fixtures/initial_data.json @@ -0,0 +1 @@ +[{"pk": 1, "model": "inventory.printertemplate", "fields": {"default": false, "item_type": 1, "printer": 2, "template": "^FX=========================\r\n^FX 3\"x3\" Label\r\n^FX=========================\r\n^XA\r\n\r\n\r\n^FX======== Left Side ===========\r\n\r\n^FX------------\r\n^FX ^LH changes the 0,0 point of all subsequent location references\r\n^FX------------\r\n\r\n^LH0,50\r\n\r\n^FX ---Header---\r\n\r\n^FO25,0\r\n^CF0,50\r\n^FB250,2,,C\r\n^FD{{ item.barcode_id }}^FS\r\n\r\n^FX ---Column 1: Flowcells---\r\n\r\n^FX-----------------\r\n^FX FB command for automatic text formatting:\r\n^FX ^FB[dot width of area], [max # of lines], [change line spacing], [justification: L, C, R, J], [hanging indent]\r\n^FX-----------------\r\n\r\n^CF0,30,30\r\n^FO75,125\r\n^FB275,19,,L\r\n^FD{% for flowcell in flowcell_id_list %}{{ flowcell }}{% if not forloop.last %}\\&{% endif %}{% endfor %}^FS\r\n^FX ---Date---\r\n\r\n^FO0,725\r\n^CF0,35\r\n^FB300,2,,C\r\n^FD{{ oldest_rundate|date:\"YMd\" }} - {{ latest_rundate|date:\"YMd\" }}^FS\r\n\r\n^FX ---Barcode---\r\n\r\n^FO135,795\r\n^BXN,3,200^FDinvb|{{ item.barcode_id }}^FS\r\n\r\n^FX======== Right Side ===========\r\n\r\n^LH300,60\r\n\r\n^FX ---Header---\r\n\r\n^FO0,0\r\n^CF0,50\r\n^FB600,2,,C\r\n^FD{{ barcode_id }}^FS\r\n\r\n^FX ---Dividing line---\r\n\r\n^FX---------------\r\n^FX GB command:\r\n^FX ^GB[box width], [box height], [border thickness], [color: B, W], [corner rounding: 0-8]^FS\r\n^FX---------------\r\n\r\n^FO0,100\r\n^GB0,600,10^FS\r\n\r\n^FX ---Column 2: Libraries 1-20---\r\n\r\n^CF0,30,30\r\n^FO75,100\r\n^FB100,20,,L\r\n^FD{% for lib_id in library_id_list_1_to_20 %}{{ lib_id }}{% if not forloop.last %}\\&{% endif %}{% endfor %}^FS\r\n\r\n^FX ---Column 3: Libraries 21-40---\r\n\r\n^CF0,30,30\r\n^FO200,100\r\n^FB100,20,,L\r\n^FD{% for lib_id in library_id_list_21_to_40 %}{{ lib_id }}{% if not forloop.last %}\\&{% endif %}{% endfor %}^FS\r\n\r\n^FX ---Column 4: Libraries 41-60---\r\n\r\n^CF0,30,30\r\n^FO325,100\r\n^FB100,20,,L\r\n^FD{% for lib_id in library_id_list_41_to_60 %}{{ lib_id }}{% if not forloop.last %}\\&{% endif %}{% endfor %}^FS\r\n\r\n^FX ---Column 5: Libraries 61-80---\r\n\r\n^CF0,30,30\r\n^FO450,100\r\n^FB100,20,,L\r\n^FD{% for lib_id in library_id_list_61_to_80 %}{{ lib_id }}{% if not forloop.last %}\\&{% endif %}{% endfor %}^FS\r\n\r\n^FX ---Date---\r\n\r\n^FO0,715\r\n^CF0,35\r\n^FB600,2,,C\r\n^FDRun Dates: {{ oldest_rundate|date:\"YMd\" }}-{{ latest_rundate|date:\"YMd\" }}^FS\r\n\r\n^FX ---Barcode---\r\n\r\n^FO255,785\r\n^BXN,3,200^FDinvb|{{ item.barcode_id }}^FS\r\n\r\n^LH0,0\r\n^FX ---End---\r\n^XZ\r\n"}}, {"pk": 2, "model": "inventory.printertemplate", "fields": {"default": true, "item_type": 2, "printer": 1, "template": "^FX=========================\r\n^FX Harddrive Location Tracking Label\r\n^FX 300x375 dots\r\n^FX=========================\r\n\r\n^XA\r\n^LH 0,25\r\n\r\n^FO0,0\r\n^CF0,35\r\n^FB375,1,,C\r\n^FD{{ item.item_type.name }}:^FS\r\n\r\n^FX -------Text contains HD serial #-------------\r\n^FO15,75\r\n^CF0,42\r\n^FB325,3,,C\r\n^FD{% if use_uuid %}{{ item.uuid }}{% else %}{{ item.barcode_id }}{% endif %}^FS\r\n\r\n^FX -------Barcode contains HD serial #-----------\r\n^FO150,200\r\n^BXN,3,200\r\n^FD{% if use_uuid %}invu|{{ item.uuid }}{% else %}invb|{{ item.barcode_id }}{% endif %}^FS\r\n\r\n^XZ\r\n"}}] diff --git a/htsworkflow/frontend/inventory/models.py b/htsworkflow/frontend/inventory/models.py index 32acf82..971c50e 100644 --- a/htsworkflow/frontend/inventory/models.py +++ b/htsworkflow/frontend/inventory/models.py @@ -5,6 +5,7 @@ from django.db.models.signals import pre_save from htsworkflow.frontend.samples.models import Library from htsworkflow.frontend.experiments.models import FlowCell +from htsworkflow.frontend.bcmagic.models import Printer try: @@ -26,6 +27,17 @@ def _assign_uuid(sender, instance, **kwargs): if instance.uuid is None or len(instance.uuid) != 32: instance.uuid = uuid.uuid1().hex +def _switch_default(sender, instance, **kwargs): + """ + When new instance has default == True, uncheck all other defaults + """ + if instance.default: + other_defaults = PrinterTemplate.objects.filter(default=True) + + for other in other_defaults: + other.default = False + other.save() + class Vendor(models.Model): name = models.CharField(max_length=256) @@ -40,7 +52,7 @@ class Location(models.Model): name = models.CharField(max_length=256, unique=True) location_description = models.TextField() - uuid = models.CharField(max_length=32, blank=True, help_text="Leave blank for automatic UUID generation") + uuid = models.CharField(max_length=32, blank=True, help_text="Leave blank for automatic UUID generation", editable=False) notes = models.TextField(blank=True, null=True) @@ -52,6 +64,7 @@ class Location(models.Model): pre_save.connect(_assign_uuid, sender=Location) + class ItemInfo(models.Model): model_id = models.CharField(max_length=256, blank=True, null=True) part_number = models.CharField(max_length=256, blank=True, null=True) @@ -77,6 +90,9 @@ class ItemInfo(models.Model): name += u"lot:%s " % (self.lot_number) return u"%s: %s" % (name, self.purchase_date) + + class Meta: + verbose_name_plural = "Item Info" class ItemType(models.Model): @@ -93,6 +109,10 @@ class ItemStatus(models.Model): def __unicode__(self): return self.name + + class Meta: + verbose_name_plural = "Item Status" + class Item(models.Model): @@ -100,7 +120,7 @@ class Item(models.Model): #Automatically assigned uuid; used for barcode if one is not provided in # barcode_id - uuid = models.CharField(max_length=32, blank=True, help_text="Leave blank for automatic UUID generation", unique=True) + uuid = models.CharField(max_length=32, blank=True, help_text="Leave blank for automatic UUID generation", unique=True, editable=False) # field for existing barcodes; used instead of uuid if provided barcode_id = models.CharField(max_length=256, blank=True, null=True) @@ -129,6 +149,26 @@ class Item(models.Model): pre_save.connect(_assign_uuid, sender=Item) +class PrinterTemplate(models.Model): + """ + Maps templates to printer to use + """ + item_type = models.ForeignKey(ItemType) + printer = models.ForeignKey(Printer) + + default = models.BooleanField() + + template = models.TextField() + + def __unicode__(self): + if self.default: + return u'%s %s' % (self.item_type.name, self.printer.name) + else: + return u'%s %s (default)' % (self.item_type.name, self.printer.name) + +pre_save.connect(_switch_default, sender=PrinterTemplate) + + class LongTermStorage(models.Model): flowcell = models.ForeignKey(FlowCell) @@ -142,6 +182,9 @@ class LongTermStorage(models.Model): def __unicode__(self): return u"%s: %s" % (str(self.flowcell), ', '.join([ str(s) for s in self.storage_devices.iterator() ])) + class Meta: + verbose_name_plural = "Long Term Storage" + class ReagentBase(models.Model): diff --git a/htsworkflow/frontend/inventory/views.py b/htsworkflow/frontend/inventory/views.py index d9d1b64..1534bfe 100644 --- a/htsworkflow/frontend/inventory/views.py +++ b/htsworkflow/frontend/inventory/views.py @@ -1,4 +1,4 @@ -from htsworkflow.frontend.inventory.models import Item, LongTermStorage +from htsworkflow.frontend.inventory.models import Item, LongTermStorage, ItemType from htsworkflow.frontend.inventory.bcmagic import item_search from htsworkflow.frontend.bcmagic.plugin import register_search_plugin from htsworkflow.frontend.experiments.models import FlowCell @@ -10,7 +10,7 @@ from htsworkflow.frontend import settings from django.core.exceptions import ObjectDoesNotExist from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render_to_response -from django.template import RequestContext +from django.template import RequestContext, Template from django.template.loader import get_template from django.contrib.auth.decorators import login_required @@ -26,20 +26,71 @@ INVENTORY_CONTEXT_DEFAULTS = { 'bcmagic': BarcodeMagicForm() } -INVENTORY_ITEM_PRINT_DEFAULTS = { - 'Hard Drive': 'inventory/hard_drive_shell.zpl', - 'default': 'inventory/default.zpl', - 'host': settings.BCPRINTER_PRINTER1_HOST +def __flowcell_rundate_sort(x, y): + """ + Sort by rundate + """ + if x.run_date > y.run_date: + return 1 + elif x.run_date == y.run_date: + return 0 + else: + return -1 + +def __expand_longtermstorage_context(context, item): + """ + Expand information for LongTermStorage + """ + flowcell_list = [] + flowcell_id_list = [] + library_id_list = [] + + for lts in item.longtermstorage_set.all(): + flowcell_list.append(lts.flowcell) + flowcell_id_list.append(lts.flowcell.flowcell_id) + library_id_list.extend([ lib.library_id for lib in lts.libraries.all() ]) + + flowcell_list.sort(__flowcell_rundate_sort) + context['oldest_rundate'] = flowcell_list[0].run_date + context['latest_rundate'] = flowcell_list[-1].run_date + + context['flowcell_id_list'] = flowcell_id_list + context['library_id_list_1_to_20'] = library_id_list[0:20] + context['library_id_list_21_to_40'] = library_id_list[20:40] + context['library_id_list_41_to_60'] = library_id_list[40:60] + context['library_id_list_61_to_80'] = library_id_list[60:80] + + +EXPAND_CONTEXT = { + 'Hard Drive': __expand_longtermstorage_context } -def getTemplateByType(item_type): +#INVENTORY_ITEM_PRINT_DEFAULTS = { +# 'Hard Drive': 'inventory/hard_drive_shell.zpl', +# 'default': 'inventory/default.zpl', +# 'host': settings.BCPRINTER_PRINTER1_HOST +#} + +def getPrinterTemplateByType(item_type): """ returns template to use given item_type """ - if item_type in INVENTORY_ITEM_PRINT_DEFAULTS: - return INVENTORY_ITEM_PRINT_DEFAULTS[item_type] + assert item_type.printertemplate_set.count() < 2 + + # Get the template for item_type + if item_type.printertemplate_set.count() > 0: + printer_template = item_type.printertemplate_set.all()[0] + return printer_template + # Get default else: - return INVENTORY_ITEM_PRINT_DEFAULTS['default'] + try: + printer_template = PrinterTemplate.objects.get(default=True) + except ObjectDoesNotExist: + msg = "No template for item type (%s) and no default template found" % (item_type.name) + raise ValueError, msg + + return printer_template + @login_required def data_items(request): @@ -134,12 +185,25 @@ def item_summary_by_uuid(request, uuid, msg='', item=None): context_instance=RequestContext(request)) + + + + +def __expand_context(context, item): + """ + If EXPAND_CONTEXT dictionary has item.item_type.name function registered, use it to expand context + """ + if item.item_type.name in EXPAND_CONTEXT: + expand_func = EXPAND_CONTEXT[item.item_type.name] + expand_func(context, item) + def _item_print(item, request): """ Prints an item given a type of item label to print """ #FIXME: Hard coding this for now... need to abstract later. context = {'item': item} + __expand_context(context, item) # Print using barcode_id if not item.force_use_uuid and (item.barcode_id is None or len(item.barcode_id.strip())): @@ -150,9 +214,11 @@ def _item_print(item, request): context['use_uuid'] = True msg = 'Printing item with UUID: %s' % (item.uuid) + printer_template = getPrinterTemplateByType(item.item_type) + c = RequestContext(request, context) - t = get_template(getTemplateByType(item.item_type.name)) - print_zpl_socket(t.render(c)) + t = Template(printer_template.template) + print_zpl_socket(t.render(c), host=printer_template.printer.ip_address) return msg diff --git a/htsworkflow/frontend/templates/inventory/hard_drive_shell.zpl b/htsworkflow/frontend/templates/inventory/hard_drive_shell.zpl index 9595e3f..610df2e 100644 --- a/htsworkflow/frontend/templates/inventory/hard_drive_shell.zpl +++ b/htsworkflow/frontend/templates/inventory/hard_drive_shell.zpl @@ -10,14 +10,14 @@ ^FX ^LH changes the 0,0 point of all subsequent location references ^FX------------ -^LH0,0 +^LH0,50 ^FX ---Header--- ^FO25,0 ^CF0,50 ^FB250,2,,C -^FD{{ barcode_id }}^FS +^FD{{ item.barcode_id }}^FS ^FX ---Column 1: Flowcells--- @@ -35,12 +35,12 @@ ^FO0,725 ^CF0,35 ^FB300,2,,C -^FDCreated {{ creation_date }}^FS +^FD{{ oldest_rundate|date:"YMd" }} - {{ latest_rundate|date:"YMd" }}^FS ^FX ---Barcode--- -^FO135,775 -^BXN,3,200^FDinvb|{{ barcode_id }}^FS +^FO135,795 +^BXN,3,200^FDinvb|{{ item.barcode_id }}^FS ^FX======== Right Side =========== @@ -68,40 +68,40 @@ ^CF0,30,30 ^FO75,100 ^FB100,20,,L -^FD{% for lib_id in library_id_list_1_to_20 %}{{ lib_id }}{% if not forloop.last %}\&{% endif %}{% endfore %}^FS +^FD{% for lib_id in library_id_list_1_to_20 %}{{ lib_id }}{% if not forloop.last %}\&{% endif %}{% endfor %}^FS ^FX ---Column 3: Libraries 21-40--- ^CF0,30,30 ^FO200,100 ^FB100,20,,L -^FD{% for lib_id in library_id_list_21_to_40 %}{{ lib_id }}{% if not forloop.last %}\&{% endif %}{% endfore %}^FS +^FD{% for lib_id in library_id_list_21_to_40 %}{{ lib_id }}{% if not forloop.last %}\&{% endif %}{% endfor %}^FS ^FX ---Column 4: Libraries 41-60--- ^CF0,30,30 ^FO325,100 ^FB100,20,,L -^FD{% for lib_id in library_id_list_41_to_60 %}{{ lib_id }}{% if not forloop.last %}\&{% endif %}{% endfore %}^FS +^FD{% for lib_id in library_id_list_41_to_60 %}{{ lib_id }}{% if not forloop.last %}\&{% endif %}{% endfor %}^FS ^FX ---Column 5: Libraries 61-80--- ^CF0,30,30 ^FO450,100 ^FB100,20,,L -^FD{% for lib_id in library_id_list_61_to_80 %}{{ lib_id }}{% if not forloop.last %}\&{% endif %}{% endfore %}^FS +^FD{% for lib_id in library_id_list_61_to_80 %}{{ lib_id }}{% if not forloop.last %}\&{% endif %}{% endfor %}^FS ^FX ---Date--- -^FO0,725 +^FO0,715 ^CF0,35 ^FB600,2,,C -^FDLast Modified {{ modified_date }}^FS +^FDRun Dates: {{ oldest_rundate|date:"YMd" }}-{{ latest_rundate|date:"YMd" }}^FS ^FX ---Barcode--- -^FO255,775 -^BXN,3,200^FDinvb|{{ barcode_id }}^FS +^FO255,785 +^BXN,3,200^FDinvb|{{ item.barcode_id }}^FS ^LH0,0 ^FX ---End--- -- 2.30.2