Skip to main content

Overview

The PortfolioItem model represents images and media in the tattoo studio’s portfolio, linking photos to artists, clients, sessions, and transactions with metadata for categorization and public display.

Model Definition

from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, ForeignKey
from sqlalchemy.orm import relationship

class PortfolioItem(Base):
    __tablename__ = "portfolio_items"

Fields

Primary Key

id
Integer
required
Unique identifier for the portfolio item

Relationships (All Nullable)

artist_id
Integer
Foreign key reference to the artistForeign Key: artists.idNullable: True (portfolio item may not be tied to specific artist)
user_id
Integer
Foreign key reference to the user who uploaded the itemForeign Key: users.idNullable: True
client_id
Integer
Foreign key reference to the client shown in the imageForeign Key: clients.idNullable: True
session_id
Integer
Foreign key reference to the tattoo sessionForeign Key: sessions.idNullable: True
transaction_id
Integer
Foreign key reference to the payment transactionForeign Key: transactions.idNullable: True

File Paths

path
String
required
File path to the full-size imageUsage: Relative or absolute path to stored image
thumb_path
String
File path to the thumbnail imageNullable: TrueUsage: Optimized smaller version for galleries

Description

caption
Text
Image caption or descriptionNullable: TrueUsage: Description, story, or context about the tattoo

Categorization Metadata

style
String
Tattoo styleNullable: TrueExamples: “Traditional”, “Realism”, “Japanese”, “Blackwork”, “Watercolor”
body_area
String
Body placement areaNullable: TrueExamples: “arm”, “back”, “leg”, “chest”, “sleeve”
color_mode
String
Color mode of the tattooNullable: TrueValid Values: “color”, “bn” (black and white)
fresh_or_healed
String
Healing status when photo was takenNullable: TrueValid Values: “fresh”, “healed”

Display Settings

is_public
Boolean
default:"true"
Whether the image is visible to publicUsage: Control visibility on public portfolio/website
is_cover
Boolean
default:"false"
Whether this image should be featured as a cover imageUsage: Flag for highlighting in galleries or artist profiles

Metadata

created_at
DateTime
default:"datetime.utcnow"
Timestamp when the portfolio item was created

Relationship Objects

artist
Artist
The Artist object this portfolio item belongs toLazy Loading: joined (eager loading)
user
User
The User object who uploaded this itemLazy Loading: joined (eager loading)
client
Client
The Client object shown in this imageLazy Loading: joined (eager loading)
session
TattooSession
The TattooSession object this image is fromLazy Loading: joined (eager loading)
transaction
Transaction
The Transaction object associated with this workLazy Loading: joined (eager loading)

Database Schema

CREATE TABLE portfolio_items (
    id INTEGER PRIMARY KEY,
    
    -- Relationships (all nullable)
    artist_id INTEGER,
    user_id INTEGER,
    client_id INTEGER,
    session_id INTEGER,
    transaction_id INTEGER,
    
    -- File paths
    path VARCHAR NOT NULL,
    thumb_path VARCHAR,
    
    -- Description
    caption TEXT,
    
    -- Categorization
    style VARCHAR,
    body_area VARCHAR,
    color_mode VARCHAR,
    fresh_or_healed VARCHAR,
    
    -- Display settings
    is_public BOOLEAN NOT NULL DEFAULT 1,
    is_cover BOOLEAN NOT NULL DEFAULT 0,
    
    -- Metadata
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (artist_id) REFERENCES artists(id),
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (client_id) REFERENCES clients(id),
    FOREIGN KEY (session_id) REFERENCES sessions(id),
    FOREIGN KEY (transaction_id) REFERENCES transactions(id)
);

Usage Examples

Create Portfolio Item from Session

from data.models.portfolio import PortfolioItem
from datetime import datetime

# Add photo after completing a session
portfolio_item = PortfolioItem(
    artist_id=1,
    user_id=2,
    client_id=5,
    session_id=10,
    path="/uploads/portfolio/2024/03/tattoo_001.jpg",
    thumb_path="/uploads/portfolio/2024/03/thumbs/tattoo_001.jpg",
    caption="Japanese dragon sleeve - first session",
    style="Japanese",
    body_area="sleeve",
    color_mode="color",
    fresh_or_healed="fresh",
    is_public=True,
    is_cover=False
)
db.add(portfolio_item)
db.commit()

Create Standalone Portfolio Item

# Add a general portfolio image not tied to a specific session
portfolio_item = PortfolioItem(
    artist_id=1,
    path="/uploads/portfolio/general/flash_design_01.jpg",
    thumb_path="/uploads/portfolio/general/thumbs/flash_design_01.jpg",
    caption="Available flash design",
    style="Traditional",
    color_mode="color",
    is_public=True
)
db.add(portfolio_item)
db.commit()

Get Public Portfolio for Artist

# Get all public portfolio items for an artist
artist_id = 1

portfolio = db.query(PortfolioItem).filter(
    PortfolioItem.artist_id == artist_id,
    PortfolioItem.is_public == True
).order_by(PortfolioItem.created_at.desc()).all()

for item in portfolio:
    print(f"{item.style or 'Uncategorized'} - {item.caption or 'No caption'}")

Get Cover Images

# Get featured/cover images
cover_images = db.query(PortfolioItem).filter(
    PortfolioItem.is_cover == True,
    PortfolioItem.is_public == True
).all()

Filter by Style

# Get all Japanese style tattoos
japanese_work = db.query(PortfolioItem).filter(
    PortfolioItem.style == "Japanese",
    PortfolioItem.is_public == True
).all()

for item in japanese_work:
    if item.artist:
        print(f"By {item.artist.name}: {item.caption}")

Filter by Body Area

# Get all sleeve tattoos
sleeves = db.query(PortfolioItem).filter(
    PortfolioItem.body_area == "sleeve",
    PortfolioItem.is_public == True
).order_by(PortfolioItem.created_at.desc()).all()

Get Client’s Tattoo History

# Get all portfolio images for a specific client
client_id = 5

client_tattoos = db.query(PortfolioItem).filter(
    PortfolioItem.client_id == client_id
).order_by(PortfolioItem.created_at).all()

print(f"Client's tattoo history:")
for item in client_tattoos:
    print(f"  {item.created_at.strftime('%Y-%m-%d')} - {item.caption}")
    if item.session:
        print(f"    Session with {item.session.artist.name}")

Toggle Privacy

# Make an image private
item = db.query(PortfolioItem).filter(PortfolioItem.id == 1).first()
item.is_public = False
db.commit()

# Make it public again
item.is_public = True
db.commit()

Set Cover Image

# Set a new cover image (unset previous covers for the artist)
artist_id = 1
new_cover_id = 10

# Remove existing cover flags for this artist
db.query(PortfolioItem).filter(
    PortfolioItem.artist_id == artist_id,
    PortfolioItem.is_cover == True
).update({"is_cover": False})

# Set new cover
item = db.query(PortfolioItem).filter(PortfolioItem.id == new_cover_id).first()
item.is_cover = True
db.commit()

Search Portfolio

# Search by caption
search_term = "dragon"

results = db.query(PortfolioItem).filter(
    PortfolioItem.caption.ilike(f"%{search_term}%"),
    PortfolioItem.is_public == True
).all()
def get_filtered_portfolio(artist_id=None, style=None, body_area=None, 
                           color_mode=None, fresh_or_healed=None):
    query = db.query(PortfolioItem).filter(
        PortfolioItem.is_public == True
    )
    
    if artist_id:
        query = query.filter(PortfolioItem.artist_id == artist_id)
    if style:
        query = query.filter(PortfolioItem.style == style)
    if body_area:
        query = query.filter(PortfolioItem.body_area == body_area)
    if color_mode:
        query = query.filter(PortfolioItem.color_mode == color_mode)
    if fresh_or_healed:
        query = query.filter(PortfolioItem.fresh_or_healed == fresh_or_healed)
    
    return query.order_by(PortfolioItem.created_at.desc()).all()

Common Categorization Values

Styles

  • Traditional
  • Japanese
  • Realism
  • Blackwork
  • Watercolor
  • Geometric
  • Tribal
  • Neo-Traditional
  • Minimalist

Body Areas

  • arm
  • leg
  • back
  • chest
  • sleeve
  • half-sleeve
  • hand
  • neck
  • foot

Color Modes

  • color - Color tattoo
  • bn - Black and white (black and grey)

Healing Status

  • fresh - Freshly done (same day)
  • healed - Fully healed (weeks/months later)

Image Management Best Practices

  1. Always generate thumbnails for better performance in galleries
  2. Use relative paths for portability
  3. Organize by date (e.g., /uploads/portfolio/YYYY/MM/)
  4. Store metadata (style, body_area) for better filtering
  5. Respect privacy - check is_public flag before displaying
  6. Link to sessions when possible for complete record-keeping
  7. Use cover images to feature best work

Notes

  • All relationship fields are nullable for maximum flexibility
  • The artist relationship has cascade delete from Artist model
  • Images can exist without being tied to any session (flash, designs)
  • Use is_public to control portfolio visibility without deleting
  • The created_at timestamp helps with chronological sorting
  • thumb_path is essential for performance in grid galleries
  • Lazy loading is set to “joined” for eager loading of relationships
  • Location in codebase: data/models/portfolio.py:7

Build docs developers (and LLMs) love