Printing
- Zebrabase allows printing customizable QR labels with substock information.
- It is recommended to use a Zebra ZD 420 series printer as it was used for testing during the development. Other Zebra printers will most likely function as well. All non-Zebra printers should have ZPL emulation (Zebra Programming Language - https://en.wikipedia.org/wiki/Zebra_Programming_Language) in order to function properly! While it is possible to use other printers and their programming language (for example Intermec), there is no support granted for these printers.
- To ensure good printing quality, use 300 dpi printers.
- You can customize your templates using the code snippets offered here.
- Zebrabase support team can help out with creating templates (only if your printer operates in ZPL II language).
Printwes
To print labels from Zebrabase, you will need printwes2 installed on your printing computer. Printwes provides communication between Zebrabase and the printer, i.e. it needs to be running in order to print. It can work in 2 modes, local and remote.
Local mode
Local mode is easier to setup but allows you to print labels only from the computer connected to your printer. To set up Printwes in local mode, install it and then in Zebrabase, set the configuration key general.printwes_url to ws://localhost:8777 (to change the key, go to Admin/
Remote mode
Remote mode allows you to send print requests to your printer from any device connected to the internet. To set up Printwes in the remote mode, you have to:
- install Printwes
- register on https://printwes.org - the username should be in the form
zebrabase_<facility prefix>(e.g.zebrabase_img) - after successful registration, you will get the "Printwes2 uri":
wss://printwes.org/ws/pwserver/<username>/?token=<access token>(no brackets!) - save this URI to Zebrabase configuration (key
general.printwes_url) - save this URI to Printwes client configuration: Printwes2 GUI --> Setup
- restart the Printwes2 client (if still not working, restart the computer)
The computer connected to the printer has to be running in order to print, and has to have a Printwes running. The PC user account with running Printwes can be locked and other user can be logged in and working on the PC.
Multiple printers
When using multiple printers in your facility, there are two setup options: using all printers in the local mode, or using all printers in the remote mode. Combining the two approaches is not possible.
Local mode
Download Printwes on all computers connected to printers. This is the only setup needed if you have each printer connected to a different computer. If you have multiple printers connected to one computer, create a profile for each printer in Admin / Codebooks / Printer. The name of the printer in Zebrabase has to correspond to the name of the printer stored on the computer (go to Printers & Scanners settings on your PC to change it). When printing, you will be asked to select which printer you wish to use (remember that the printer has to be connected to the computer you are using).
Remote mode
Download Printwes on all computers connected to printers and save the Printwes URI to each of the clients. Create a profile for each printer in Admin / Codebooks / Printer. The name of the printer in Zebrabase has to correspond to the name of the printer stored on the computer (go to Printers & Scanners settings on your PC to change it).
Printing templates
There are three codebooks related to printing in the Codebooks section (Admin/
- Printer type
- Printing template
- Printer
The field Printer type is just a label that serves for better orientation if you are using multiple different printers. It should be used to distinguish the brand or the programming language of your printers (e.g. Zebra and Intermec). Printer type can be assigned to your printers in the Printer codebook. It is also required for printing templates.
The printing template is printer-type specific. The template itself uses the templating system Jinja and you can use all the built-in filters and structures like loops, conditions, macros, etc., and some of our custom filters.
Printer: Printwes2 allows users to connect multiple printers and choose which printer will be used for printing. You don't have to register any printer in Zebrabase if you are using only one.
There is also a configuration key substock.detail.label_template that determines which template will be used for the label preview in substock detail. Go to Admin/
Data obtainable from Zebrabase
base_url- URL of your Zebrabase instance (i.e. https://demo.zebrabase.org); useful for generating QR codessubstock- all the data we know about the substock:id- substock ID (SSID)stock_id- stock ID (LSID)date_of_birth- date of birthname_gen- full generated namefacility_name_gen- shorter generated name used in facility viewlist_name_gen- shorter generated name used in list viewgenotypes_zygosities- list of genotypes and their zygosities:genotype_name_gen,zygosity_namefishline_name- fishline aliasfishline_name_gen- fishline generated nameowners- list of owners:username,first_name,last_nameresponsible- responsible userworkgroups- list of workgroup details:nameand some otherfunding_name- fundingproject- project detail ornull- project detail:code,id,name,name_gen(code + name),leader, and some otherstatus- productivity statustags- list of tagslight_name- light statusdiet_name- diet statussubstock_num- substock number (within sibling substocks)generation- number of generationposition_fullname- full position including room and rackposition_name- position within rackroom_name- roomrack_name- rackspecies_name- speciessex_name- sexcount- fish countsuffix- substock suffix- ... and some more. You can use anything contained in API response on endpoint
/api/substock/<some_id>/?detail(example in API docs), or just write{{ substock }}into the template field and the full response will be returned
parents- list of parent substocks
Zebrabase specific filters
substock_name_no_html- this filter removes all html tags and puts the upper index into bracketsunidecode- transcribes UTF characters to the most similar ASCIIsmart_split(max_row_length, max_row_count=10, delimiter=',')- attempts to split a string value by thedelimiterand to build rows of text out of the parts; if the resulting row count extendsmax_row_count, standard split byNcharacters is used instead (see example bellow); available from 3.1.2
ZPL basics
This is the basic information needed to customize your QR labels written in ZPL. For more details, go to ZPL programming guide.
^XA,^XZ- start/end of label^CF- sets font for the whole label (syntax: ^CF0,25,20 font,height[,width]) (font 0 is well scalable and recommended)^A- sets font only for the next entry (syntax: ^A0N,25,20 font orientation,height[,width], orientation can be either N = normal, R = 90 degrees, or B = 270 degrees)^FO- position of the following field - it is in absolute coordinates from the left and top edge (x, y)^FD- text string for the field^FS- end of field definition^PW- sets width of label^BQ- QR code (syntax: ^BQU2,5 - the last variable indicates the size of the code and can possess values 1-10)
The parts between square brackets - [...] - are optional, they can be omitted. If these parts are specified, the square brackets are omitted.
Code snippets
These snippets are examples of how you can get some basic data for the labels. You can mix and match them to create your custom label, just define the position of each field in front. It is also possible to specify the font. The full code describing one field could look like this:
^A0,40,40
^FO200,170
^FDDOB: {{ substock.date_of_birth }}^FS
font
position
text
Code snippets
{% set ss_name = substock.facility_name_gen | substock_name_no_html | unidecode %}
Split substock name to more lines (3 rows with 40 characters in each)
{{ ss_name[:40] }}`, `{{ ss_name[40:80] }}`, `{{ ss_name[80:] }}
Declaration of printer-friendly version of substock name + splitting using smart_split (40 characters per row, 3 rows)
{% set ss_name = substock.facility_name_gen | substock_name_no_html | unidecode | smart_split(40, 3) %}
List of owners
^FDOW: {{ substock.owners | map(attribute='username') | join(', ') }}^FS
QR code (size 5)
^BQU,2,5
^FDLA,{{ base_url }}/substock/{{ substock.id }}^FS
Date of birth
^FDDOB: {{ substock.date_of_birth }}^FS
Full ID (LSID-SSID)
^FDID: {{ substock.stock_id }}-{{ substock.id }}^FS
Position
^FDPOS: {{ substock.position_fullname }}^FS
Project code
^FDPRJ: {{ substock.project.code }}^FS
F-generation
{% if substock.generation == None %}
^FDF: {{ substock.generation }}^FS
{% else %}
^FDF: F{{ substock.generation }}^FS
{% endif %}
Substock description (35 characters maximum)
^FDDES: {{ substock.description[:35] }}^FS
Fish count
^FDN: {{ substock.count}}^FS
List of tags
^FDTAG: {{ substock.tags | map(attribute='name') | join(',') }}^FS
List of genotypes
^FDGNT: {{ substock.genotypes_zygosities | map(attribute='genotype_name_gen') | join(', ') | substock_name_no_html }}^FS
First genotype in list and its zygosity
^FD{{ substock.genotypes_zygosities[0].genotype_name_gen | substock_name_no_html }}^FS
^FD{{ substock.genotypes_zygosities[0].zygosity_name }}^FS
Printing of images
If you want to have an image included in your label, you can create a ZPL command for the image for example by using Labelary. Paste your label template into the viewer, click "Add image" and select your image from a file.
Conditions and loops
You can use any functions available in Jinja in your label templates, including if statements and for loops:
{% if substock.generation == None %}
^FDF: no input^FS
{% elif substock.generation == 0 %}
^FDF: F0 (primary generation)^FS
{% else %}
^FDF: F{{ substock.generation }}^FS
{% endif %}
^FD
{% for genotype in substock.genotypes_zygosities %}
{{ genotype.genotype_name_gen | substock_name_no_html }} {{ genotype.zygosity_name }};
{% endfor %}
^FS
Examples of use
How smart_split works
# second and third sections are short enough to fit in one row
{% set x = '1234567,123,456,123456' | smart_split(10, 3) %}
{{ x[0] }}
{{ x[1] }}
{{ x[2] }}
1234567,
123,456,
123456
# second section is longer than 10 characters --> it's split
{% set x = '123,12345678987,123,456' | smart_split(10, 3) %}
{{ x[0] }}
{{ x[1] }}
{{ x[2] }}
123,
1234567898
7,123,456
# `x` would have 4 rows in this example
# so smart split is replaced by normal split
{% set x = '123,1234567898,12345,456,789' | smart_split(10, 3) %}
{{ x[0] }}
{{ x[1] }}
{{ x[2] }}
123,123456
7898,12345
,456,789
Example (very simple)
{% set ss_name = substock.facility_name_gen | substock_name_no_html | unidecode %}
^XA
^CFN,20
^PW450
^LL0300
^FO21,130
^BQN,2,5
^FDLA,{{ base_url }}/substock/{{ substock.id }}^FS
^FO11,20
^A0N
^FD{{ ss_name[:40] }}^FS
^FO11,50
^A0N
^FD{{ ss_name[40:80] }}^FS
^FO11,80
^A0N
^FD{{ ss_name[80:] }}^FS
^FO185,140
^FDDOB: {{ substock.date_of_birth }}^FS
^FO185,170
^FDID: {{ substock.stock_id }}-{{ substock.id }}^FS
^FO347,170
^FD[{{ substock.substock_num }}]^FS
^FO185,200
^FDPOS: {{ substock.position_fullname }}^FS
^FO185,230
^FDOW: {{ substock.owners | map(attribute='username') | join(', ') }}^FS
^FO185,260
^FDPRJ: {{ substock.project.code }}^FS
^XZ
Example with smart_split
{% set ss_name = substock.facility_name_gen | substock_name_no_html | unidecode | smart_split(40, 3) %}
^XA
^CFN,20
^PW450
^LL0300
^FO21,130
^BQN,2,5
^FDLA,{{ base_url }}/substock/{{ substock.id }}^FS
^FO11,20
^FD{{ ss_name[0] }}^FS
^FO11,50
^FD{{ ss_name[1] }}^FS
^FO11,80
^FD{{ ss_name[2] }}^FS
^FO185,140
^FDDOB: {{ substock.date_of_birth }}^FS
^FO185,170
^FDID: {{ substock.stock_id }}-{{ substock.id }}^FS
^FO347,170
^FD[{{ substock.substock_num }}]^FS
^FO185,200
^FDPOS: {{ substock.position_fullname }}^FS
^FO185,230
^FDOW: {{ substock.owners | map(attribute='username') | join(', ') }}^FS
^FO185,260
^FDPRJ: {{ substock.project.code }}^FS
^XZ
ss_name is not a string but an array of strings. Standard indices 0, 1, 2, ... are used instead of range indices :40, 40:80, 80: used in the previous example.
Example (custom 1)
{% set ss_name = substock.facility_name_gen | substock_name_no_html | unidecode | smart_split(40) %}
^XA
^CF0,22
^PW450
^LL0300
^FO11,110
^BQN,2,5
^FDLA,{{ base_url }}/substock/{{ substock.id }}^FS
^A0,25,25
^FO11,20
^FD{{ ss_name[0] }}^FS
^A0,25,25
^FO11,50
^FD{{ ss_name[1] }}^FS
^A0,25,25
^FO11,80
^FD{{ ss_name[2] }}^FS
^FO175,122
^FDDOB: {{ substock.date_of_birth }}^FS
^FO175,152
{% if substock.generation == None %}
^FDF: {{ substock.generation }}^FS
{% else %}
^FDF: F{{ substock.generation }}^FS
{% endif %}
^FO175,182
^FDEthic code:^FS
^FO175,212
^FDN animals: {{ substock.count }}^FS
^FO175,242
^FDProject code: {{ substock.project.code }}^FS
^XZ
Example (custom 2)
{% set ss_name = substock.facility_name_gen | substock_name_no_html | unidecode %}
^XA
^CFN,22
^PW456
^LL0256
^A0,23,25
^FO11,10
^FD{{ ss_name[:33] }}^FS
^A0,23,25
^FO11,35
^FD{{ ss_name[33:66] }}^FS
^A0,23,25
^FO11,60
^FD{{ ss_name[66:] }}^FS
^A0,27,29
^FO11,98
^FDID={{ substock.local_stock_id }} n={{ substock.count}} POS: {{ substock.position_fullname }}^FS
^A0,23,25
^FO11,126
^FDTAG: {{ substock.tags | map(attribute='name') | join(',') }}^FS
^A0,23,25
^FO11,150
^FDDES: {{ substock.description[:35] }}^FS
^A0,23,25
^FO65,174
^FD{{ substock.description[35:] }}^FS
^A0,23,25
^FO11,198
^FDGEN: {{ substock.generation }}^FS
^A0,23,25
^FO11,222
^FDDOB: {{ substock.date_of_birth }} OW: {{ substock.owners | map(attribute='username') | join(', ') }}^FS
^XZ
Example (custom 3)
{% set ss_name = substock.facility_name_gen | substock_name_no_html | unidecode | smart_split(30, 3) %}
^XA
^CF0
^PW800
^LT20
^FO40,155
^BQU,2,5
^FDLA,{{ base_url }}/substock/{{ substock.id }}^FS
^A0,45,45
^FO40,15
^FD{{ ss_name[0] }}^FS
^A0,45,45
^FO40,65
^FD{{ ss_name[1] }}^FS
^A0,45,45
^FO40,115
^FD{{ ss_name[2] }}^FS
^A0,40,40
^FO200,170
^FDDOB: {{ substock.date_of_birth }}^FS
^A0,40,40
^FO200,220
^FDID: {{ substock.stock_id }}-{{ substock.id }} ^FS
^A0,40,40
^FO200,270
^FDPOS: {{ substock.position_fullname }}^FS
^XZ
Example (custom 4)
{% set ss_name = substock.facility_name_gen | substock_name_no_html | unidecode | replace(" (?)", "") | smart_split(30, 3) %}
^XA
^CF0N,35
^PW550
^LL300
^FO0,20
^FD{{ ss_name[0] }}^FS
^FO0,65
^FD{{ ss_name[1] }}^FS
^FO0,110
^FD{{ ss_name[2] }}^FS
^FO0,165
^FD{{ substock.owners | map(attribute='username') | join(', ') }}^FS
^FO0,215
^FDDOB (Y-M-D): {{ substock.date_of_birth }}^FS
^FO0,250
^FDID:{{ substock.stock_id }}-{{ substock.id }}^FS
^FO0,250
^XZ
.png)