Building a Zine Catalog
This tutorial guides you through building a complete zine cataloging system from scratch. You'll learn how to plan your catalog, set up the structure, import data, and maintain it over time.
What You'll Build
A complete cataloging system that tracks:
- Zines (the intellectual works)
- Creators (people, collectives, organizations)
- Holdings (physical copies you own)
- Repository (your collection itself)
Our Scenario
You're starting a community zine library called "Radical Reads" in Chicago. You have:
- 50 zines to catalog
- A mix of perzines, political zines, and art zines
- Multiple copies of some zines
- A mix of creators (individuals and collectives)
- Plans to lend zines and allow photocopying
Phase 1: Planning
Step 1: Define Your Repository
First, document your library itself:
{
"repo_id": "radical-reads-chicago",
"repository_name": "Radical Reads",
"repository_kind": "Library",
"description": "Community-run lending library focused on radical and DIY zines, with emphasis on Chicago voices and social justice themes.",
"location": {
"city": "Chicago",
"region": "Illinois",
"country": "United States"
},
"website": ["https://radicalreads.example.org"],
"email": "[email protected]",
"specializations": [
"Chicago zine culture",
"Social justice zines",
"DIY and punk zines",
"Feminist and queer zines"
]
}
Save this as repository.json.
Step 2: Choose Your ID Schemes
Decide how you'll assign IDs:
Repository IDs:
- One repository: radical-reads-chicago
Zine IDs:
- Pattern: zine-[number] (e.g., zine-001, zine-002)
- Alternative: [creator-lastname]-[title-slug] (e.g., chen-mental-health)
Agent IDs:
- Pattern: agent-[number] or [name-slug] (e.g., agent-001 or riley-chen)
Holding IDs:
- Pattern: hold-[zine-id]-[copy-num] (e.g., hold-001-1, hold-001-2)
- Or use barcodes if you have them
Step 3: Create a Cataloging Workflow
- Examine the zine
- Create or find creator record (AgentCore2)
- Create zine record (ZineCore2)
- Create holding record for your copy (HoldingCore2)
- Validate all records
- Add to your system
Phase 2: Creating Records
Example 1: Simple Perzine
Physical zine in hand: "Bike Dreams" by Alex Gomez, 2023, 20 pages, freely duplicatable
Step 1: Creator
{
"agent_id": "alex-gomez",
"display_name": "Alex Gomez",
"agent_kind": "Person",
"location": "Chicago, Illinois, United States"
}
Save as agents/alex-gomez.json.
Step 2: Zine
{
"zine_id": "zine-001",
"title": "Bike Dreams",
"creator": ["alex-gomez"],
"subject": ["travel", "diy"],
"format": "print",
"date": "2023",
"genre": ["perzine"],
"abstract": "Personal zine about bike touring through the Midwest, with tips for budget travel and bike maintenance.",
"number_of_pages": 20,
"physical_dimensions": "5.5 x 8.5 inches",
"binding_features": ["stapled"],
"language": ["eng"],
"place_of_publication": "Chicago, Illinois, United States",
"rights": "freely-duplicatable"
}
Save as zines/zine-001.json.
Step 3: Holding
{
"holding_id": "hold-001-1",
"zine_id": "zine-001",
"repository_id": "radical-reads-chicago",
"call_number": "TRAVEL-001",
"copy_number": "1",
"condition_note": "Excellent condition",
"location_details": "Cabinet A, Shelf 2",
"access_status": "Loanable",
"distro_status": "Can duplicate"
}
Save as holdings/hold-001-1.json.
Example 2: Collective Zine
Zine details: "Our Voices" by Chicago Zine Collective, 2024, compilation of 10 contributors
Step 1: Creator (Collective)
{
"agent_id": "chicago-zine-collective",
"display_name": "Chicago Zine Collective",
"agent_kind": "Collective",
"biography": "Volunteer-run collective that creates compilation zines featuring Chicago zinesters. Active since 2020.",
"location": "Chicago, Illinois, United States",
"active_dates": "2020-present",
"website": ["https://chicagozines.example.org"]
}
Step 2: Zine
{
"zine_id": "zine-002",
"title": "Our Voices",
"series_title": ["Chicago Zine Collective Annual"],
"issue_designation": "Volume 4",
"creator": ["chicago-zine-collective"],
"subject": ["art", "poetry", "activism"],
"format": "print",
"date": "2024",
"genre": ["compilation-zine", "literary-zine"],
"abstract": "Annual compilation zine featuring writing, art, and comics from 10 Chicago-based zinesters.",
"number_of_pages": 48,
"physical_dimensions": "8.5 x 11 inches",
"binding_features": ["saddle-stitched"],
"language": ["eng"],
"place_of_publication": "Chicago, Illinois, United States",
"publisher": ["chicago-zine-collective"],
"rights": "cc-by-nc-sa",
"table_of_contents": [
"Introduction by the Collective",
"Poetry by 5 contributors",
"Comics by 3 contributors",
"Essays by 2 contributors"
]
}
Step 3: Holding
{
"holding_id": "hold-002-1",
"zine_id": "zine-002",
"repository_id": "radical-reads-chicago",
"call_number": "COMP-001",
"copy_number": "1 of 2",
"condition_note": "Excellent condition, brand new",
"location_details": "New arrivals shelf",
"access_status": "Loanable",
"distro_status": "Can duplicate",
"public_notes": "Donated by the collective, March 2024"
}
Phase 3: Organizing Your Files
Create a directory structure:
radical-reads-catalog/
├── repository.json
├── agents/
│ ├── alex-gomez.json
│ ├── chicago-zine-collective.json
│ └── ...
├── zines/
│ ├── zine-001.json
│ ├── zine-002.json
│ └── ...
└── holdings/
├── hold-001-1.json
├── hold-002-1.json
└── ...
Phase 4: Bulk Import Preparation
For your 50 zines, you might start with a spreadsheet:
Example Spreadsheet Columns
| zine_id | title | creator_name | date | subject | genre | pages | rights | condition | location |
|---|---|---|---|---|---|---|---|---|---|
| zine-001 | Bike Dreams | Alex Gomez | 2023 | travel;diy | perzine | 20 | freely-duplicatable | excellent | Cabinet A |
| zine-002 | Our Voices | Chicago Zine Collective | 2024 | art;poetry;activism | compilation-zine;literary-zine | 48 | cc-by-nc-sa | excellent | New arrivals |
Convert to JSON
Write a script to convert CSV to JSON:
import csv
import json
# Read spreadsheet
with open('zines.csv', 'r') as f:
reader = csv.DictReader(f)
zines = list(reader)
# Convert each row to ZineCore2 JSON
for row in zines:
zine_record = {
"zine_id": row['zine_id'],
"title": row['title'],
"creator": [row['creator_name']], # Will need to match to agent_id later
"subject": row['subject'].split(';'),
"genre": row['genre'].split(';'),
"format": "print",
"date": row['date'],
"number_of_pages": int(row['pages']),
"rights": row['rights']
}
# Save to file
with open(f"zines/{row['zine_id']}.json", 'w') as outfile:
json.dump(zine_record, outfile, indent=2)
# Also create holding record
holding_record = {
"holding_id": f"hold-{row['zine_id']}-1",
"zine_id": row['zine_id'],
"repository_id": "radical-reads-chicago",
"call_number": row['zine_id'].upper(),
"condition_note": row['condition'],
"location_details": row['location'],
"access_status": "Loanable",
"distro_status": "Can duplicate"
}
with open(f"holdings/hold-{row['zine_id']}-1.json", 'w') as outfile:
json.dump(holding_record, outfile, indent=2)
Phase 5: Using the Reference Implementation
Setup Django Server
Follow the installation guide:
# Clone repository
git clone https://github.com/zpublishing/zinecore2-api
cd zinecore2-api
# Setup environment
./onboarding.sh
# Start server
./start.sh
Import Your Records via API
import requests
import json
import glob
API_BASE = "http://localhost:8000/api"
TOKEN = "your-auth-token-here"
headers = {"Authorization": f"Token {TOKEN}"}
# 1. Create repository
with open('repository.json') as f:
repo = json.load(f)
response = requests.post(f"{API_BASE}/repositories/", json=repo, headers=headers)
print(f"Created repository: {response.status_code}")
# 2. Create agents
for agent_file in glob.glob('agents/*.json'):
with open(agent_file) as f:
agent = json.load(f)
response = requests.post(f"{API_BASE}/agents/", json=agent, headers=headers)
print(f"Created agent {agent['agent_id']}: {response.status_code}")
# 3. Create zines
for zine_file in glob.glob('zines/*.json'):
with open(zine_file) as f:
zine = json.load(f)
response = requests.post(f"{API_BASE}/zines/", json=zine, headers=headers)
print(f"Created zine {zine['zine_id']}: {response.status_code}")
# 4. Create holdings
for holding_file in glob.glob('holdings/*.json'):
with open(holding_file) as f:
holding = json.load(f)
response = requests.post(f"{API_BASE}/holdings/", json=holding, headers=headers)
print(f"Created holding {holding['holding_id']}: {response.status_code}")
Phase 6: Maintaining Your Catalog
Adding New Zines
- Examine zine
- Check if creator exists (search API or your files)
- Create new agent if needed
- Create zine record
- Create holding record
- Validate and submit
Updating Records
When you discover new information:
# Update a zine record
zine_id = "zine-001"
updates = {
"abstract": "Updated description with more detail...",
"table_of_contents": ["Section 1", "Section 2"]
}
response = requests.patch(
f"{API_BASE}/zines/{zine_id}/",
json=updates,
headers=headers
)
Tracking Circulation
Add notes to holdings when zines are loaned:
{
"holding_id": "hold-001-1",
"public_notes": "Checked out to member #42 on 2024-03-15. Returned 2024-03-22."
}
Or use your library system's circulation module if available.
Handling Damaged or Lost Items
Update the holding:
# Mark as damaged
updates = {
"condition_note": "Damaged: water damage on cover. Pages intact but wrinkled.",
"access_status": "Reference" # Change from Loanable to Reference
}
response = requests.patch(
f"{API_BASE}/holdings/hold-001-1/",
json=updates,
headers=headers
)
# Or mark as lost
updates = {
"condition_note": "Lost as of 2024-03-20. Last seen on loan.",
"access_status": "Restricted"
}
Phase 7: Public Access
Building a Search Interface
Create a simple search page:
// Fetch all zines
async function searchZines(query) {
const response = await fetch(
`http://localhost:8000/api/zines/?search=${query}`
);
const data = await response.json();
return data.results;
}
// Display results
async function displayResults() {
const query = document.getElementById('search').value;
const zines = await searchZines(query);
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = zines.map(zine => `
<div class="zine">
<h3>${zine.title}</h3>
<p>By ${zine.creator.join(', ')}</p>
<p>${zine.abstract}</p>
<p>Subjects: ${zine.subject.join(', ')}</p>
</div>
`).join('');
}
Export Options
Provide downloads in multiple formats:
# Export to CSV for patrons
import csv
response = requests.get(f"{API_BASE}/zines/", headers={"Accept": "text/csv"})
with open('catalog.csv', 'w') as f:
f.write(response.text)
# Export JSON-LD for archives
response = requests.get(f"{API_BASE}/zines/", headers={"Accept": "application/ld+json"})
with open('catalog.jsonld', 'w') as f:
f.write(response.text)
Best Practices
Consistency
- Use consistent naming patterns for IDs
- Always fill out the same fields for similar zines
- Use vocabulary terms consistently
Documentation
- Keep a README.md explaining your cataloging decisions
- Document your ID schemes
- Note any local practices
Backup
# Backup all JSON files
tar -czf backup-$(date +%Y%m%d).tar.gz zines/ agents/ holdings/ repository.json
# Or backup database if using reference implementation
pg_dump zinecore > backup-$(date +%Y%m%d).sql
Quality Control
- Validate records regularly
- Check for duplicates
- Review records quarterly for updates
Scaling Up
For Larger Collections
- Use UUIDs instead of sequential IDs
- Implement batch import scripts
- Consider dedicated database instead of JSON files
- Use the reference implementation API
- Set up automated backups
For Multiple Staff
- Create cataloging guidelines document
- Use version control (git) for JSON files
- Implement review workflow
- Train staff on vocabulary terms
Common Challenges
Challenge: Multiple Creators
Solution: Create agent records for each, list all in creator array
Challenge: Unknown Information
Solution: Use "unknown" for rights, leave optional fields empty
Challenge: Inconsistent Naming
Solution: Use alternative_names in AgentCore2 to link variants
Challenge: Hard to Categorize
Solution: Use public_notes to explain your decisions
Next Steps
- Review the API Reference for advanced features
- Learn about filtering and search
- Explore output formats
- Set up deployment to production
Resources
- ZineCore2 Profile
- AgentCore2 Profile
- HoldingCore2 Profile
- RepoCore2 Profile
- All Vocabularies
- Reference Implementation
You now have a complete cataloging system! Happy cataloging! 📚✨