Implemented custom templates by inventory type using PrinterTemplate object.
authorBrandon King <kingb@caltech.edu>
Sat, 22 Aug 2009 00:08:55 +0000 (00:08 +0000)
committerBrandon King <kingb@caltech.edu>
Sat, 22 Aug 2009 00:08:55 +0000 (00:08 +0000)
  * 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
htsworkflow/frontend/bcmagic/fixtures/initial_data.json
htsworkflow/frontend/bcmagic/models.py
htsworkflow/frontend/inventory/admin.py
htsworkflow/frontend/inventory/fixtures/initial_data.json [new file with mode: 0644]
htsworkflow/frontend/inventory/models.py
htsworkflow/frontend/inventory/views.py
htsworkflow/frontend/templates/inventory/hard_drive_shell.zpl

index 2dce2ec5936a417e06bcb8be8119c7c5f61f18b7..073c3e85da54b09416128e73786207dd20144afa 100644 (file)
@@ -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
index 09af6542ff4b1bd0235ae542183c20bbf8fd16b7..179732030045c7ef16e5d4d640f849c821543a11 100644 (file)
@@ -1 +1 @@
-[{"pk": 1, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<uuid>[A-Fa-f0-9]+)", "url_template": "/samples/freezer/{{ uuid }}/", "keyword": "frzr"}}, {"pk": 2, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<uuid>[A-Fa-f0-9]+)", "url_template": "/samples/container/{{ uuid }}/", "keyword": "cntr"}}, {"pk": 3, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<sampleid>\\d+)\\|(?P<owner>[A-Za-z0-9_\\- ]+)", "url_template": "/samples/sample/{{ sampleid }}/", "keyword": "s"}}, {"pk": 4, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<search>[\\S\\s]+)", "url_template": "http://www.google.com/search?q={{ search }}", "keyword": "gg"}}, {"pk": 5, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<search>[\\S\\s]+)", "url_template": "http://www.flickr.com/search/?q={{ search }}", "keyword": "flickr"}}, {"pk": 6, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<uuid>[A-Fa-f0-9]+)", "url_template": "/inventory/{{ uuid }}/", "keyword": "invu"}}, {"pk": 7, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<barcode_id>.+)", "url_template": "/inventory/{{barcode_id}}/", "keyword": "invb"}}]
+[{"pk": 1, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<uuid>[A-Fa-f0-9]+)", "url_template": "/samples/freezer/{{ uuid }}/", "keyword": "frzr"}}, {"pk": 2, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<uuid>[A-Fa-f0-9]+)", "url_template": "/samples/container/{{ uuid }}/", "keyword": "cntr"}}, {"pk": 3, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<sampleid>\\d+)\\|(?P<owner>[A-Za-z0-9_\\- ]+)", "url_template": "/samples/sample/{{ sampleid }}/", "keyword": "s"}}, {"pk": 4, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<search>[\\S\\s]+)", "url_template": "http://www.google.com/search?q={{ search }}", "keyword": "gg"}}, {"pk": 5, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<search>[\\S\\s]+)", "url_template": "http://www.flickr.com/search/?q={{ search }}", "keyword": "flickr"}}, {"pk": 6, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<uuid>[A-Fa-f0-9]+)", "url_template": "/inventory/{{ uuid }}/", "keyword": "invu"}}, {"pk": 7, "model": "bcmagic.keywordmap", "fields": {"regex": "(?P<barcode_id>.+)", "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"}}]
index 12d873694ceb942878e321bcdb51df7469c009a0..4db58a3ddd8e5b27bfcc539cae7fd12fa92a4f74 100644 (file)
@@ -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
index 38b3fee024f568797b1c0e5970143cd6510e6fb2..7aa0fe547e2147a8382eccc949bcd82bb5544094 100644 (file)
@@ -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 (file)
index 0000000..ae0ae12
--- /dev/null
@@ -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"}}]
index 32acf8270f4d77ac74af7b93891c1bd4f6803c89..971c50e631de2f759a769b5363276ba330dc7635 100644 (file)
@@ -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):
index d9d1b6419d7972212722cf6ce229743c07926d08..1534bfef56acac07c5cd8fb318ec99504ac30ad6 100644 (file)
@@ -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
 
index 9595e3f32a36ae05dbefce05acefbf948ca6bb87..610df2e645678e827ee62705c2e404c525b2c94d 100644 (file)
 ^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---
 
 ^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 ===========
 
 ^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---