diff --git a/.dockerignore b/.dockerignore
index cd49c72..d7d4b6b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -7,7 +7,6 @@ db/
*.pyc
__pycache__
django/importer/uploadeddata/*
-django/attachments/attachment/*
_scratches
exports.sh
.last_git_commit
diff --git a/.gitignore b/.gitignore
index 9ca9901..0c41d79 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,11 +5,9 @@ labelbase.log
labelbase.log.*
bgt.log
db/
-backup_*
*.pyc
__pycache__
django/importer/uploadeddata/*
-django/attachments/attachment/*
#_scratches
exports.sh
.last_git_commit
diff --git a/BACKUP_AND_MIGRATION_GUIDE.md b/BACKUP_AND_MIGRATION_GUIDE.md
deleted file mode 100644
index 59b71b8..0000000
--- a/BACKUP_AND_MIGRATION_GUIDE.md
+++ /dev/null
@@ -1,896 +0,0 @@
-# Labelbase Backup and Migration Guide
-
-A comprehensive guide for safely backing up your Labelbase database and running Django migrations.
-
-## Table of Contents
-- [Why Backup?](#why-backup)
-- [Quick Backup](#quick-backup)
-- [Automated Backup Script](#automated-backup-script)
-- [Running Migrations Safely](#running-migrations-safely)
-- [Upgrading Labelbase](#upgrading-labelbase)
-- [Restoring from Backup](#restoring-from-backup)
-- [Scheduled Backups](#scheduled-backups)
-- [Best Practices](#best-practices)
-
----
-
-## Why Backup?
-
-**Always backup before running migrations!** Migrations can:
-- Alter database table structures in irreversible ways
-- Delete data if there are bugs in the migration code
-- Fail mid-execution, leaving your database inconsistent
-- Introduce conflicts with existing data
-
-A backup takes 30 seconds. Recovery without one could take hours or days.
-
----
-
-## Quick Backup
-
-From your Labelbase directory:
-
-```bash
-# Navigate to Labelbase directory
-cd Labelbase
-
-# Source environment variables
-source exports.sh
-
-# Create backup with timestamp
-docker-compose exec -T labelbase_mysql mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" labelbase > backup_$(date +%Y%m%d_%H%M%S).sql
-```
-
-This creates a backup file like `backup_20250119_143022.sql`.
-
-### Backup config.ini (Important!)
-
-The `config.ini` file contains encryption keys and other critical settings. Always back it up too:
-
-```bash
-# Navigate to Labelbase directory
-cd Labelbase
-
-# Source environment variables
-source exports.sh
-
-# Create backup with timestamp
-docker-compose exec -T labelbase_django cat /app/config.ini > backup_$(date +%Y%m%d_%H%M%S)_config.ini
-```
-
-This creates a backup file like `backup_20250119_143022_config.ini`.
-
----
-
-## Automated Backup Script
-
-Create a reusable backup script that handles everything automatically.
-
-### Create the Script
-
-Save this as `backup-labelbase.sh` in your Labelbase directory:
-
-```bash
-#!/bin/bash
-
-# Labelbase Database Backup Script
-# Usage: ./backup-labelbase.sh
-
-# Configuration
-LABELBASE_DIR="/path/to/Labelbase" # CHANGE THIS to your actual path
-BACKUP_DIR="$LABELBASE_DIR/backups"
-KEEP_BACKUPS=10 # Number of backups to keep
-
-# Colors for output
-GREEN='\033[0;32m'
-RED='\033[0;31m'
-YELLOW='\033[1;33m'
-NC='\033[0m' # No Color
-
-# Change to Labelbase directory
-cd "$LABELBASE_DIR" || exit 1
-
-# Source environment variables for MySQL passwords
-if [ ! -f "exports.sh" ]; then
- echo -e "${RED}✗ Error: exports.sh not found!${NC}"
- echo "Make sure you're in the Labelbase directory and exports.sh exists."
- exit 1
-fi
-
-source exports.sh
-
-# Check if MySQL password is set
-if [ -z "$MYSQL_ROOT_PASSWORD" ]; then
- echo -e "${RED}✗ Error: MYSQL_ROOT_PASSWORD not set!${NC}"
- echo "Make sure exports.sh contains the MySQL password."
- exit 1
-fi
-
-# Create backup directory if it doesn't exist
-mkdir -p "$BACKUP_DIR"
-
-# Generate backup filename with timestamp
-TIMESTAMP=$(date +%Y%m%d_%H%M%S)
-BACKUP_FILE="$BACKUP_DIR/labelbase_backup_$TIMESTAMP.sql"
-CONFIG_BACKUP_FILE="$BACKUP_DIR/config_backup_$TIMESTAMP.ini"
-
-echo -e "${YELLOW}Starting backup...${NC}"
-echo "Database backup: $BACKUP_FILE"
-echo "Config backup: $CONFIG_BACKUP_FILE"
-
-# Backup config.ini first (contains encryption keys!)
-echo "Backing up config.ini..."
-docker-compose exec -T labelbase_django cat /app/config.ini > "$CONFIG_BACKUP_FILE"
-
-if [ $? -eq 0 ] && [ -s "$CONFIG_BACKUP_FILE" ]; then
- echo -e "${GREEN}✓ Config backup successful${NC}"
-else
- echo -e "${YELLOW}⚠ Config backup failed or file is empty${NC}"
-fi
-
-# Create database backup
-docker-compose exec -T labelbase_mysql mysqldump \
- -u root \
- -p"${MYSQL_ROOT_PASSWORD}" \
- --single-transaction \
- --quick \
- --lock-tables=false \
- labelbase > "$BACKUP_FILE"
-
-# Check if backup was successful
-if [ $? -eq 0 ] && [ -s "$BACKUP_FILE" ]; then
- echo -e "${GREEN}✓ Backup successful!${NC}"
-
- # Get file size
- SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
- echo "Backup size: $SIZE"
-
- # Compress backup to save space
- echo "Compressing backup..."
- gzip "$BACKUP_FILE"
-
- if [ $? -eq 0 ]; then
- COMPRESSED_SIZE=$(du -h "${BACKUP_FILE}.gz" | cut -f1)
- echo -e "${GREEN}✓ Compressed to: $COMPRESSED_SIZE${NC}"
- echo "Backup location: ${BACKUP_FILE}.gz"
- else
- echo -e "${YELLOW}⚠ Compression failed, keeping uncompressed backup${NC}"
- fi
-
- # Clean up old backups (keep only last N backups)
- echo "Cleaning up old backups (keeping last $KEEP_BACKUPS)..."
- BACKUP_COUNT=$(ls -1 "$BACKUP_DIR"/labelbase_backup_*.sql.gz 2>/dev/null | wc -l)
-
- if [ "$BACKUP_COUNT" -gt "$KEEP_BACKUPS" ]; then
- ls -t "$BACKUP_DIR"/labelbase_backup_*.sql.gz | tail -n +$((KEEP_BACKUPS + 1)) | xargs -r rm
- echo -e "${GREEN}✓ Cleaned up old backups${NC}"
- else
- echo "No cleanup needed ($BACKUP_COUNT backups exist)"
- fi
-
- echo ""
- echo -e "${GREEN}=== Backup Complete ===${NC}"
- echo "Database: ${BACKUP_FILE}.gz"
- echo "Config: ${CONFIG_BACKUP_FILE}"
-
-else
- echo -e "${RED}✗ Backup failed!${NC}"
-
- # Remove empty or failed backup file
- [ -f "$BACKUP_FILE" ] && rm "$BACKUP_FILE"
-
- echo "Troubleshooting:"
- echo "1. Check if MySQL container is running: docker-compose ps"
- echo "2. Check MySQL logs: docker-compose logs labelbase_mysql"
- echo "3. Verify password in exports.sh"
- exit 1
-fi
-```
-
-### Make Script Executable
-
-```bash
-chmod +x backup-labelbase.sh
-```
-
-### Edit Configuration
-
-Open `backup-labelbase.sh` and change this line to your actual Labelbase path:
-
-```bash
-LABELBASE_DIR="/path/to/Labelbase" # CHANGE THIS!
-```
-
-For example:
-```bash
-LABELBASE_DIR="/root/Labelbase"
-# or
-LABELBASE_DIR="/home/username/Labelbase"
-```
-
-### Run the Backup
-
-```bash
-./backup-labelbase.sh
-```
-
-You'll see output like:
-```
-Starting backup...
-Backup file: /path/to/Labelbase/backups/labelbase_backup_20250119_143022.sql
-✓ Backup successful!
-Backup size: 15M
-Compressing backup...
-✓ Compressed to: 3.2M
-Backup location: /path/to/Labelbase/backups/labelbase_backup_20250119_143022.sql.gz
-Cleaning up old backups (keeping last 10)...
-No cleanup needed (3 backups exist)
-
-=== Backup Complete ===
-Location: /path/to/Labelbase/backups/labelbase_backup_20250119_143022.sql.gz
-```
-
----
-
-## Running Migrations Safely
-
-**Always follow this order:**
-
-### Step 1: Create a Backup
-
-```bash
-./backup-labelbase.sh
-```
-
-### Step 2: Check Migration Status
-
-```bash
-source exports.sh
-docker-compose exec labelbase_django python manage.py showmigrations
-```
-
-This shows which migrations are applied (marked with `[X]`) and pending (marked with `[ ]`).
-
-### Step 3: Review Pending Migrations
-
-Look for any unapplied migrations. If you see pending migrations for critical apps, review them carefully.
-
-### Step 4: Apply Migrations
-
-```bash
-# If you've modified models, create new migrations first
-docker-compose exec labelbase_django python manage.py makemigrations
-
-# Apply all pending migrations
-docker-compose exec labelbase_django python manage.py migrate
-```
-
-### Step 5: Verify Application
-
-After migrations complete:
-1. Check for any error messages
-2. Visit your Labelbase site
-3. Test critical functionality
-4. Check Django logs: `docker-compose logs labelbase_django`
-
-### Step 6: If Something Goes Wrong
-
-If migrations fail or break functionality, restore from backup (see below).
-
----
-
-## Upgrading Labelbase
-
-When new versions of Labelbase are released, follow this workflow to safely upgrade.
-
-### Complete Upgrade Workflow
-
-**Step 1: Backup First (Critical!)**
-
-```bash
-cd Labelbase
-./backup-labelbase.sh
-```
-
-**Step 2: Pull Latest Code**
-
-```bash
-git pull origin master
-```
-
-**Step 3: Rebuild Containers (if needed)**
-
-If dependencies or Docker configuration changed:
-
-```bash
-source exports.sh && docker-compose up --build -d
-```
-
-Or use the main script:
-
-```bash
-source exports.sh && ./build-and-run-labelbase.sh
-```
-
-**⚠️ IMPORTANT**: These commands are SAFE - they rebuild containers but preserve your data in Docker volumes. Your database and uploaded files are NOT deleted.
-
-**❌ DANGER ZONE - Commands that DELETE data:**
-```bash
-# NEVER run these unless you want to lose ALL data:
-docker-compose down -v # The -v flag deletes volumes = data loss!
-docker volume prune # Deletes unused volumes
-docker system prune -a # Nuclear option - deletes everything
-```
-
-**Step 4: Apply Migrations and collect static files **
-
-```bash
-source exports.sh
-docker-compose exec labelbase_django python manage.py showmigrations
-docker-compose exec labelbase_django python manage.py migrate
-docker-compose exec labelbase_django python manage.py collectstatic --noinput
-
-```
-
-**Step 5: Restart Services**
-
-```bash
-docker-compose restart labelbase_django
-```
-
-**Step 6: Verify Everything Works**
-
-1. Visit your Labelbase site
-2. Test critical functionality
-3. Check logs: `docker-compose logs -f labelbase_django`
-
-### Quick Upgrade Script
-
-Create `update-and-migrate.sh` for a streamlined upgrade process:
-
-```bash
-#!/bin/bash
-
-# Labelbase Quick Update & Migration Script
-# Usage: source exports.sh && ./update-and-migrate.sh
-
-# Colors
-GREEN='\033[0;32m'
-RED='\033[0;31m'
-YELLOW='\033[1;33m'
-NC='\033[0m'
-
-echo -e "${YELLOW}=== Labelbase Update & Migrate ===${NC}"
-
-# Check if we're in the right directory
-if [ ! -f "docker-compose.yml" ]; then
- echo -e "${RED}Error: Not in Labelbase directory (docker-compose.yml not found)${NC}"
- exit 1
-fi
-
-# Check env vars
-if [[ -z "${MYSQL_ROOT_PASSWORD}" ]]; then
- echo -e "${RED}Error: Run 'source exports.sh' first!${NC}"
- exit 1
-fi
-
-# Step 1: Backup
-echo -e "${YELLOW}Step 1: Creating backup...${NC}"
-if [ -f "backup-labelbase.sh" ]; then
- ./backup-labelbase.sh
- if [ $? -ne 0 ]; then
- echo -e "${RED}Backup failed! Aborting upgrade.${NC}"
- exit 1
- fi
-else
- echo -e "${YELLOW}⚠ Warning: backup-labelbase.sh not found, skipping backup${NC}"
- read -p "Continue without backup? (yes/no): " -r
- if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
- echo "Upgrade cancelled."
- exit 0
- fi
-fi
-
-# Step 2: Pull latest code
-echo ""
-echo -e "${YELLOW}Step 2: Pulling latest changes...${NC}"
-git pull origin master
-
-if [ $? -ne 0 ]; then
- echo -e "${RED}Git pull failed!${NC}"
- exit 1
-fi
-
-# Step 3: Check for pending migrations
-echo ""
-echo -e "${YELLOW}Step 3: Checking for migrations...${NC}"
-PENDING=$(docker-compose exec -T labelbase_django python manage.py showmigrations --plan 2>/dev/null | grep "\[ \]" | wc -l)
-
-if [ $PENDING -gt 0 ]; then
- echo -e "${YELLOW}Found $PENDING pending migration(s)${NC}"
-
- # Show what will be migrated
- echo "Pending migrations:"
- docker-compose exec -T labelbase_django python manage.py showmigrations | grep "\[ \]"
-
- echo ""
- read -p "Apply migrations now? (y/n): " -n 1 -r
- echo
-
- if [[ $REPLY =~ ^[Yy]$ ]]; then
- echo "Applying migrations..."
- docker-compose exec -T labelbase_django python manage.py migrate --noinput
-
- if [ $? -eq 0 ]; then
- echo -e "${GREEN}✓ Migrations applied successfully${NC}"
- else
- echo -e "${RED}✗ Migrations failed!${NC}"
- echo "Check logs: docker-compose logs labelbase_django"
- exit 1
- fi
- else
- echo -e "${YELLOW}⚠ Skipping migrations${NC}"
- echo "Run manually later: docker-compose exec labelbase_django python manage.py migrate"
- fi
-else
- echo -e "${GREEN}✓ No pending migrations${NC}"
-fi
-
-# Step 4: Restart Django
-echo ""
-echo -e "${YELLOW}Step 4: Restarting Django...${NC}"
-docker-compose restart labelbase_django
-
-echo ""
-echo -e "${GREEN}=== Update Complete! ===${NC}"
-echo "Check logs: docker-compose logs -f labelbase_django"
-echo "Visit your site to verify everything works"
-```
-
-Make it executable:
-
-```bash
-chmod +x update-and-migrate.sh
-```
-
-### Using the Quick Upgrade Script
-
-```bash
-# Navigate to Labelbase
-cd Labelbase
-
-# Source environment and run update
-source exports.sh && ./update-and-migrate.sh
-```
-
-The script will:
-1. ✓ Create automatic backup
-2. ✓ Pull latest code from git
-3. ✓ Detect pending migrations
-4. ✓ Ask for confirmation before applying
-5. ✓ Restart services
-6. ✓ Provide verification steps
-
-### When to Rebuild vs. Restart
-
-**Just restart** (`docker-compose restart`) when:
-- Only Django code changed (Python files)
-- No dependency updates
-- No Dockerfile changes
-- Fastest option
-
-**Full rebuild** (`docker-compose up --build -d`) when:
-- requirements.txt changed
-- Dockerfile modified
-- New system packages needed
-- Docker configuration changed
-
-**Data Safety Note**: Both `restart` and `--build` are SAFE - they preserve your data. Docker stores your database and files in **volumes** that persist across rebuilds.
-
-If unsure, rebuild - it's safer and only takes a minute longer.
-
-### What Actually Deletes Data
-
-Only these commands delete data (requires `-v` flag):
-
-```bash
-# DANGER: This deletes ALL data including database!
-docker-compose down -v
-
-# To safely stop without deleting data, use:
-docker-compose down # Safe - keeps volumes
-docker-compose stop # Safe - just stops containers
-```
-
-**Rule of thumb**: If you see `-v` flag, your data is at risk!
-
-### Rollback After Failed Upgrade
-
-If something goes wrong:
-
-```bash
-# 1. Stop services
-docker-compose down
-
-# 2. Restore previous code
-git reset --hard HEAD~1
-
-# 3. Restore database
-./restore-labelbase.sh backups/labelbase_backup_TIMESTAMP.sql.gz
-
-# 4. Restart
-source exports.sh && docker-compose up -d
-```
-
----
-
-## Restoring from Backup
-
-If something goes wrong, you can restore your database from a backup.
-
-### Quick Restore
-
-```bash
-# Navigate to Labelbase directory
-cd Labelbase
-
-# Source environment variables
-source exports.sh
-
-# Stop Django to prevent conflicts
-docker-compose stop labelbase_django
-
-# Decompress and restore backup
-gunzip -c backups/labelbase_backup_20250119_143022.sql.gz | \
- docker-compose exec -T labelbase_mysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}" labelbase
-
-# Restart all services
-docker-compose up -d
-
-# Check logs
-docker-compose logs -f labelbase_django
-```
-
-### Restore Script (Optional)
-
-Create `restore-labelbase.sh`:
-
-```bash
-#!/bin/bash
-
-# Labelbase Database Restore Script
-# Usage: ./restore-labelbase.sh
-
-LABELBASE_DIR="/path/to/Labelbase" # CHANGE THIS
-BACKUP_FILE="$1"
-
-# Colors
-GREEN='\033[0;32m'
-RED='\033[0;31m'
-YELLOW='\033[1;33m'
-NC='\033[0m'
-
-# Check if backup file provided
-if [ -z "$BACKUP_FILE" ]; then
- echo -e "${RED}✗ Error: No backup file specified${NC}"
- echo "Usage: ./restore-labelbase.sh "
- echo ""
- echo "Available backups:"
- ls -lh "$LABELBASE_DIR/backups/"*.sql.gz 2>/dev/null || echo "No backups found"
- exit 1
-fi
-
-# Check if backup file exists
-if [ ! -f "$BACKUP_FILE" ]; then
- echo -e "${RED}✗ Error: Backup file not found: $BACKUP_FILE${NC}"
- exit 1
-fi
-
-# Change to Labelbase directory
-cd "$LABELBASE_DIR" || exit 1
-
-# Source environment variables
-source exports.sh
-
-echo -e "${YELLOW}⚠ WARNING: This will overwrite your current database!${NC}"
-echo "Backup file: $BACKUP_FILE"
-read -p "Are you sure you want to continue? (yes/no): " -r
-echo
-
-if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
- echo "Restore cancelled."
- exit 0
-fi
-
-echo "Stopping Django container..."
-docker-compose stop labelbase_django
-
-echo "Restoring database..."
-
-# Check if file is compressed
-if [[ "$BACKUP_FILE" == *.gz ]]; then
- gunzip -c "$BACKUP_FILE" | \
- docker-compose exec -T labelbase_mysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}" labelbase
-else
- cat "$BACKUP_FILE" | \
- docker-compose exec -T labelbase_mysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}" labelbase
-fi
-
-if [ $? -eq 0 ]; then
- echo -e "${GREEN}✓ Database restored successfully${NC}"
-else
- echo -e "${RED}✗ Restore failed${NC}"
- exit 1
-fi
-
-echo "Restarting services..."
-docker-compose up -d
-
-# Optional: Restore config.ini if you have a backup from the same time
-# CONFIG_FILE="${BACKUP_FILE%_backup_*}_config_backup_${BACKUP_FILE##*_backup_}"
-# CONFIG_FILE="${CONFIG_FILE%.sql.gz}.ini"
-# if [ -f "$CONFIG_FILE" ]; then
-# echo "Found config backup: $CONFIG_FILE"
-# read -p "Restore config.ini too? (y/n) " -n 1 -r
-# echo
-# if [[ $REPLY =~ ^[Yy]$ ]]; then
-# cat "$CONFIG_FILE" | docker-compose exec -T labelbase_django bash -c "cat > /app/config.ini"
-# echo -e "${GREEN}✓ Config restored${NC}"
-# docker-compose restart labelbase_django
-# fi
-# fi
-
-echo ""
-echo -e "${GREEN}=== Restore Complete ===${NC}"
-echo "Check logs with: docker-compose logs -f labelbase_django"
-```
-
-Make executable and configure:
-```bash
-chmod +x restore-labelbase.sh
-# Edit LABELBASE_DIR in the script
-```
-
-Usage:
-```bash
-./restore-labelbase.sh backups/labelbase_backup_20250119_143022.sql.gz
-```
-
----
-
-## Scheduled Backups
-
-### Using Cron (Linux/Unix)
-
-Automate daily backups at 2 AM:
-
-```bash
-# Edit crontab
-crontab -e
-
-# Add this line (adjust path to your Labelbase directory)
-0 2 * * * cd /path/to/Labelbase && ./backup-labelbase.sh >> /var/log/labelbase-backup.log 2>&1
-```
-
-This runs the backup script daily at 2:00 AM and logs output to `/var/log/labelbase-backup.log`.
-
-### Verify Cron Job
-
-```bash
-# List current cron jobs
-crontab -l
-
-# Check backup log
-tail -f /var/log/labelbase-backup.log
-```
-
-### Alternative: Weekly Backups
-
-```bash
-# Every Sunday at 3 AM
-0 3 * * 0 cd /path/to/Labelbase && ./backup-labelbase.sh >> /var/log/labelbase-backup.log 2>&1
-```
-
----
-
-## Best Practices
-
-### Before Migrations
-1. ✓ **Always create a backup first**
-2. ✓ Review what migrations will be applied
-3. ✓ Have a rollback plan ready
-4. ✓ Test migrations on a development copy if possible
-5. ✓ Schedule migrations during low-traffic periods
-
-### Backup Storage
-1. ✓ Keep backups in multiple locations
-2. ✓ Regularly test your restore process
-3. ✓ Keep at least 7-10 recent backups
-4. ✓ Store critical backups off-server (external drive, cloud storage)
-5. ✓ Monitor backup script success/failure
-
-### Security
-1. ✓ Protect `exports.sh` - it contains database passwords
-2. ✓ Secure backup files - they contain all your data
-3. ✓ Protect `config.ini` backups - they contain encryption keys
-4. ✓ Use appropriate file permissions:
- ```bash
- chmod 600 exports.sh
- chmod 700 backups/
- chmod 600 backups/*.sql.gz
- chmod 600 backups/*_config.ini
- ```
-
-### Regular Maintenance
-1. ✓ Run backups before any system updates
-2. ✓ Test restore process quarterly
-3. ✓ Monitor backup file sizes (unexpected changes may indicate issues)
-4. ✓ Keep backup logs for troubleshooting
-
-### Docker Data Safety
-1. ✓ **SAFE commands** (preserve data):
- - `docker-compose up --build -d` - Rebuild containers
- - `docker-compose restart` - Restart services
- - `docker-compose down` - Stop without deleting volumes
- - `docker-compose stop` - Pause containers
-2. ✓ **DANGEROUS commands** (delete data):
- - `docker-compose down -v` - ⚠️ Deletes ALL volumes/data
- - `docker volume prune` - ⚠️ Removes unused volumes
- - `docker system prune -a` - ⚠️ Nuclear option
-3. ✓ **Remember**: The `-v` flag means "delete volumes" = data loss!
-
----
-
-## Troubleshooting
-
-### "MYSQL_ROOT_PASSWORD not set"
-
-**Cause**: `exports.sh` not sourced or doesn't contain password
-
-**Solution**:
-```bash
-# Check if exports.sh exists
-ls -la exports.sh
-
-# Source it
-source exports.sh
-
-# Verify password is set
-echo $MYSQL_ROOT_PASSWORD
-```
-
-### "Access denied for user 'root'"
-
-**Cause**: Wrong password in `exports.sh`
-
-**Solution**:
-1. Check MySQL container logs: `docker-compose logs labelbase_mysql`
-2. Verify password in `exports.sh` matches what MySQL expects
-3. If lost, you may need to reset MySQL root password
-
-### Backup File is Empty or Very Small
-
-**Cause**: MySQL container not running or database empty
-
-**Solution**:
-```bash
-# Check container status
-docker-compose ps
-
-# Check MySQL logs
-docker-compose logs labelbase_mysql
-
-# Verify database exists
-docker-compose exec labelbase_mysql mysql -u root -p -e "SHOW DATABASES;"
-```
-
-### Restore Fails with "Unknown Database"
-
-**Cause**: Database doesn't exist in MySQL
-
-**Solution**:
-```bash
-# Create database first
-docker-compose exec labelbase_mysql mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS labelbase;"
-
-# Then restore
-./restore-labelbase.sh backups/your_backup.sql.gz
-```
-
----
-
-## Complete Safe Migration Workflow
-
-Here's the complete workflow combining backup and migration:
-
-```bash
-# 1. Navigate to Labelbase
-cd Labelbase
-
-# 2. Create backup
-./backup-labelbase.sh
-
-# 3. Source environment
-source exports.sh
-
-# 4. Check what migrations will run
-docker-compose exec labelbase_django python manage.py showmigrations
-
-# 5. Apply migrations
-docker-compose exec labelbase_django python manage.py makemigrations
-docker-compose exec labelbase_django python manage.py migrate
-
-# 6. Check for errors
-docker-compose logs labelbase_django | tail -50
-
-# 7. Test your application
-# Visit site and verify functionality
-
-# 8. If problems occur, restore:
-# ./restore-labelbase.sh backups/labelbase_backup_TIMESTAMP.sql.gz
-```
-
----
-
-## Quick Command Reference
-
-```bash
-# Quick upgrade (recommended)
-source exports.sh && ./update-and-migrate.sh
-
-# Manual upgrade workflow
-./backup-labelbase.sh
-git pull origin master
-source exports.sh && docker-compose up --build -d
-docker-compose exec labelbase_django python manage.py migrate
-docker-compose restart labelbase_django
-
-# Create backup (database + config.ini)
-./backup-labelbase.sh
-
-# Manual database backup
-docker-compose exec -T labelbase_mysql mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" labelbase > backup_$(date +%Y%m%d_%H%M%S).sql
-
-# Manual config.ini backup
-docker-compose exec -T labelbase_django cat /app/config.ini > backup_$(date +%Y%m%d_%H%M%S)_config.ini
-
-# List backups
-ls -lh backups/
-
-# Check migration status
-docker-compose exec labelbase_django python manage.py showmigrations
-
-# Apply migrations
-docker-compose exec labelbase_django python manage.py migrate
-
-# Restore backup
-./restore-labelbase.sh backups/labelbase_backup_20250119_143022.sql.gz
-
-# View recent Django logs
-docker-compose logs labelbase_django | tail -100
-
-# Access MySQL directly
-docker-compose exec labelbase_mysql mysql -u root -p labelbase
-```
-
----
-
-## Additional Resources
-
-- [Labelbase Development Guide](DEVELOPMENT_GUIDE.md)
-- [Django Migrations Documentation](https://docs.djangoproject.com/en/stable/topics/migrations/)
-- [mysqldump Documentation](https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html)
-
----
-
-## Support
-
-If you encounter issues:
-1. Check the troubleshooting section above
-2. Review Docker logs: `docker-compose logs -f`
-3. Verify all services are running: `docker-compose ps`
-4. Check Labelbase GitHub issues: https://github.com/Labelbase/Labelbase/issues
-
----
-
-**Remember: A backup today saves recovery tomorrow. Always backup before migrations!**
diff --git a/BARE_METALL_INSTALL.md b/BARE_METALL_INSTALL.md
deleted file mode 100644
index ddc2c0c..0000000
--- a/BARE_METALL_INSTALL.md
+++ /dev/null
@@ -1,357 +0,0 @@
-# Labelbase Bare Metal Installation Guide
-
-This guide will help you install and run Labelbase directly on macOS and Linux systems, based on the RaspiBlitz installation script.
-
-## Prerequisites
-
-### Common Requirements
-- Git
-- Python 3.8 or higher
-- pip (Python package manager)
-- virtualenv
-- MySQL/MariaDB server
-
-### System-Specific Requirements
-
-#### macOS
-```bash
-# Install Homebrew if not already installed
-/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
-# Install required packages
-brew install python3 mysql git
-brew install pkg-config mysql-client
-```
-
-#### Linux (Ubuntu/Debian)
-```bash
-# Update package list
-sudo apt update
-
-# Install required packages
-sudo apt install -y \
- mariadb-server \
- mariadb-client \
- default-libmysqlclient-dev \
- build-essential \
- python3 \
- python3-pip \
- python3-venv \
- git \
- libpcre3-dev
-```
-
-#### Linux (CentOS/RHEL/Fedora)
-```bash
-# For CentOS/RHEL 8+
-sudo dnf install -y \
- mariadb-server \
- mariadb-devel \
- python3 \
- python3-pip \
- python3-virtualenv \
- git \
- gcc \
- gcc-c++ \
- make
-
-# For older versions, use yum instead of dnf
-```
-
-## Installation Steps
-
-### 1. Create Application User (Optional but Recommended)
-
-#### macOS
-```bash
-# Create a new user (optional on macOS for development)
-sudo dscl . -create /Users/labelbase
-sudo dscl . -create /Users/labelbase UserShell /bin/bash
-sudo dscl . -create /Users/labelbase RealName "Labelbase User"
-sudo dscl . -create /Users/labelbase UniqueID 1001
-sudo dscl . -create /Users/labelbase PrimaryGroupID 20
-sudo dscl . -create /Users/labelbase NFSHomeDirectory /Users/labelbase
-sudo createhomedir -c -u labelbase
-```
-
-#### Linux
-```bash
-# Create system user
-sudo adduser --system --group --shell /bin/bash --home /home/labelbase labelbase
-sudo -u labelbase cp -r /etc/skel/. /home/labelbase/
-```
-
-### 2. Start Database Service
-
-#### macOS
-```bash
-# Start MySQL service
-brew services start mysql
-
-# Secure the installation
-mysql_secure_installation
-```
-
-#### Linux
-```bash
-# Start MariaDB service
-sudo systemctl enable mariadb
-sudo systemctl start mariadb
-
-# Secure the installation
-sudo mysql_secure_installation
-```
-
-### 3. Download and Setup Labelbase
-
-```bash
-# Switch to labelbase user (if created) or use your regular user
-# sudo su - labelbase # (if using dedicated user)
-
-# Set variables
-LABELBASE_HOME="$HOME" # or /home/labelbase if using dedicated user
-LABELBASE_REPO="https://github.com/Labelbase/Labelbase/"
-LABELBASE_VERSION="2.2.1"
-
-# Clone the repository
-git clone $LABELBASE_REPO $LABELBASE_HOME/labelbase
-cd $LABELBASE_HOME/labelbase
-
-# Checkout specific version
-git checkout $LABELBASE_VERSION
-
-# Create virtual environment
-python3 -m venv $LABELBASE_HOME/ENV
-
-# Activate virtual environment
-source $LABELBASE_HOME/ENV/bin/activate
-
-# Install Python dependencies
-pip install --upgrade pip
-pip install --no-cache-dir -r $LABELBASE_HOME/labelbase/django/requirements.txt
-```
-
-### 4. Database Configuration
-
-```bash
-# Generate a secure password
-MYSQL_PASSWORD=$(openssl rand -base64 32 | tr -d '+/' | fold -w 32 | head -n 1)
-
-# Create exports file
-cat > $LABELBASE_HOME/exports.sh << EOF
-export MYSQL_PASSWORD=$MYSQL_PASSWORD
-export DATABASE_URL=mysql://ulabelbase:$MYSQL_PASSWORD@localhost:3306/labelbase
-EOF
-
-chmod 755 $LABELBASE_HOME/exports.sh
-```
-
-#### Create Database and User
-
-##### macOS
-```bash
-# Connect to MySQL
-mysql -u root -p
-
-# In MySQL prompt:
-CREATE DATABASE labelbase;
-CREATE USER 'ulabelbase'@'localhost' IDENTIFIED BY 'YOUR_GENERATED_PASSWORD';
-GRANT ALL PRIVILEGES ON labelbase.* TO 'ulabelbase'@'localhost';
-FLUSH PRIVILEGES;
-EXIT;
-```
-
-##### Linux
-```bash
-# Connect to MariaDB
-sudo mysql
-
-# In MariaDB prompt:
-CREATE DATABASE labelbase;
-CREATE USER 'ulabelbase'@'localhost' IDENTIFIED BY 'YOUR_GENERATED_PASSWORD';
-GRANT ALL PRIVILEGES ON labelbase.* TO 'ulabelbase'@'localhost';
-FLUSH PRIVILEGES;
-EXIT;
-```
-
-**Note:** Replace `YOUR_GENERATED_PASSWORD` with the password generated in the previous step.
-
-### 5. Django Setup
-
-```bash
-# Navigate to Django directory
-cd $LABELBASE_HOME/labelbase/django
-
-# Activate virtual environment and load environment variables
-source $LABELBASE_HOME/ENV/bin/activate
-source $LABELBASE_HOME/exports.sh
-
-# Run Django migrations
-python manage.py makemigrations --noinput
-python manage.py migrate --noinput
-python manage.py collectstatic --noinput
-
-# Create a superuser (optional)
-python manage.py createsuperuser
-```
-
-### 6. Running Labelbase
-
-#### Development Mode
-```bash
-# Activate environment
-source $LABELBASE_HOME/ENV/bin/activate
-source $LABELBASE_HOME/exports.sh
-
-# Navigate to Django directory
-cd $LABELBASE_HOME/labelbase/django
-
-# Run development server
-python manage.py runserver 0.0.0.0:8089
-
-# Access at: http://localhost:8089
-```
-
-#### Production Mode (using Gunicorn)
-```bash
-# Install Gunicorn if not already installed
-pip install gunicorn
-
-# Run with Gunicorn
-source $LABELBASE_HOME/ENV/bin/activate
-source $LABELBASE_HOME/exports.sh
-cd $LABELBASE_HOME/labelbase/django
-
-gunicorn labellabor.wsgi:application -b 0.0.0.0:8089 --reload
-```
-
-## Creating System Services (Optional)
-
-### macOS (using LaunchAgent)
-
-Create a plist file at `~/Library/LaunchAgents/com.labelbase.app.plist`:
-
-```xml
-
-
-
-
- Label
- com.labelbase.app
- ProgramArguments
-
- /bin/bash
- -c
- cd /Users/labelbase/labelbase/django && source /Users/labelbase/ENV/bin/activate && source /Users/labelbase/exports.sh && gunicorn labellabor.wsgi:application -b 0.0.0.0:8089
-
- RunAtLoad
-
- KeepAlive
-
- StandardOutPath
- /Users/labelbase/labelbase.out
- StandardErrorPath
- /Users/labelbase/labelbase.err
-
-
-```
-
-Load the service:
-```bash
-launchctl load ~/Library/LaunchAgents/com.labelbase.app.plist
-```
-
-### Linux (using systemd)
-
-Create `/etc/systemd/system/labelbase.service`:
-
-```ini
-[Unit]
-Description=Labelbase Application
-After=mariadb.service
-
-[Service]
-Type=simple
-User=labelbase
-WorkingDirectory=/home/labelbase/labelbase/django
-Environment="HOME_PATH=/home/labelbase"
-ExecStart=/bin/bash -c 'source /home/labelbase/ENV/bin/activate && source /home/labelbase/exports.sh && gunicorn labellabor.wsgi:application -b 0.0.0.0:8089'
-Restart=always
-TimeoutSec=120
-RestartSec=30
-
-[Install]
-WantedBy=multi-user.target
-```
-
-Enable and start the service:
-```bash
-sudo systemctl enable labelbase
-sudo systemctl start labelbase
-sudo systemctl status labelbase
-```
-
-## Troubleshooting
-
-### Common Issues
-
-1. **Database Connection Errors**
- - Ensure MySQL/MariaDB is running
- - Verify database credentials in `exports.sh`
- - Check if the database user has proper permissions
-
-2. **Python Package Issues**
- - Make sure virtual environment is activated
- - Try upgrading pip: `pip install --upgrade pip`
- - Install packages one by one if requirements.txt fails
-
-3. **Port Already in Use**
- - Change the port in the run command: `gunicorn ... -b 0.0.0.0:8090`
- - Find and kill processes using the port: `lsof -ti:8089 | xargs kill -9`
-
-4. **Permission Errors**
- - Ensure proper ownership of files: `sudo chown -R labelbase:labelbase /home/labelbase`
- - Check file permissions: `chmod 755 /home/labelbase/exports.sh`
-
-### Logs and Debugging
-
-- **Development server logs**: Displayed in terminal
-- **Gunicorn logs**: Use `--log-file` flag for logging
-- **System service logs** (Linux): `sudo journalctl -u labelbase -f`
-- **Database logs**: Check MySQL/MariaDB error logs
-
-### Stopping the Application
-
-```bash
-# If running in development mode
-Ctrl+C
-
-# If running as system service
-# macOS:
-launchctl unload ~/Library/LaunchAgents/com.labelbase.app.plist
-
-# Linux:
-sudo systemctl stop labelbase
-```
-
-## Security Considerations
-
-1. **Firewall Configuration**
- - Only expose necessary ports
- - Consider using a reverse proxy (nginx/apache) for production
-
-2. **Database Security**
- - Use strong passwords
- - Limit database user permissions
- - Consider encrypting database connections
-
-3. **Application Security**
- - Keep dependencies updated
- - Use HTTPS in production
- - Regular security updates
-
-4. **File Permissions**
- - Ensure proper file ownership and permissions
- - Limit access to configuration files
-
-This guide provides a foundation for running Labelbase on bare metal systems. Adjust paths, ports, and configurations according to your specific needs and security requirements.
diff --git a/DEVELOPMENT_GUIDE.md b/DEVELOPMENT_GUIDE.md
deleted file mode 100644
index 18baa1a..0000000
--- a/DEVELOPMENT_GUIDE.md
+++ /dev/null
@@ -1,312 +0,0 @@
-# Labelbase Development Guide
-
-Quick reference for working with and developing Labelbase in Docker.
-
-## Initial Setup
-
-### 1. Clone and Setup
-```bash
-git clone https://github.com/Labelbase/Labelbase/
-cd Labelbase
-```
-
-### 2. Generate MySQL Passwords
-```bash
-./make-exports.sh
-```
-
-This creates `exports.sh` with random passwords. **Backup this file!**
-
-### 3. Build and Run
-```bash
-source exports.sh && ./build-and-run-labelbase.sh
-```
-
-The `source exports.sh` loads the passwords into your shell, then the script uses them.
-
-Access at: http://127.0.0.1:8080
-
----
-
-## Daily Development Workflow
-
-### Start/Stop Services
-```bash
-# Start (always source exports.sh first!)
-source exports.sh && docker-compose up -d
-
-# Stop
-docker-compose down
-
-# Rebuild and restart (after code changes)
-source exports.sh && docker-compose up --build -d
-
-# Or use the main script
-source exports.sh && ./build-and-run-labelbase.sh
-```
-
-### View Logs
-```bash
-# All services
-docker-compose logs -f
-
-# Specific service
-docker-compose logs -f labelbase_django
-docker-compose logs -f labelbase_mysql
-docker-compose logs -f labelbase_nginx
-
-# Search logs
-docker-compose logs labelbase_django | grep -i error
-```
-
-### Access Container Shell
-```bash
-# Django container (most common)
-docker-compose exec labelbase_django bash
-
-# MySQL container
-docker-compose exec labelbase_mysql bash
-
-# Nginx container
-docker-compose exec labelbase_nginx sh
-```
-
-### Django Management Commands
-```bash
-# From host
-docker-compose exec labelbase_django python manage.py
-
-# Or from inside container
-docker-compose exec labelbase_django bash
-python manage.py makemigrations
-python manage.py migrate
-python manage.py createsuperuser
-python manage.py shell
-```
-
-### Database Operations
-```bash
-# Access MySQL CLI
-docker-compose exec labelbase_mysql mysql -u ulabelbase -p labelbase
-
-# Backup database
-docker-compose exec labelbase_mysql mysqldump -u root -p labelbase > backup.sql
-
-# Restore database
-docker-compose exec -T labelbase_mysql mysql -u root -p labelbase < backup.sql
-```
-
----
-
-## Common Tasks
-
-### Reset config.ini (if passwords change)
-```bash
-docker-compose exec labelbase_django bash
-rm /app/config.ini
-python manage.py make_config
-exit
-docker-compose restart labelbase_django
-```
-
-### Clean Rebuild (fresh start, deletes volumes!!)
-
-❌ DANGER ZONE
-
-```bash
-docker-compose down -v
-source exports.sh && docker-compose up --build -d
-```
-
-⚠️ **Warning**: `-v` deletes volumes including database data!
-
-### Check What Django Sees
-```bash
-# Check environment variables
-docker-compose exec labelbase_django env | grep MYSQL
-
-# Check config.ini
-docker-compose exec labelbase_django cat /app/config.ini
-
-# Check database connection
-docker-compose exec labelbase_django python manage.py dbshell
-```
-
-### Update from Git
-```bash
-git pull origin master
-source exports.sh && ./build-and-run-labelbase.sh
-```
-
-The script automatically detects updates and rebuilds if needed.
-
----
-
-## Configuration Files
-
-### exports.sh (IMPORTANT - backup this!)
-```bash
-#!/bin/bash
-export MYSQL_ROOT_PASSWORD="your_password_here"
-export MYSQL_PASSWORD="your_password_here"
-```
-- Generated by `./make-exports.sh`
-- Contains MySQL passwords
-- Source before running docker-compose: `source exports.sh`
-- **Add to `.gitignore`** - contains secrets!
-
-### config.ini (auto-generated, persisted)
-- Located at `/app/config.ini` inside Django container
-- Generated from environment variables on first run by `python manage.py make_config`
-- **Not overwritten** on subsequent runs (preserves user settings)
-- Force regenerate: `rm /app/config.ini` then restart
-
-### docker-compose.yml
-Uses environment variables like `${MYSQL_PASSWORD}` from your shell (after sourcing exports.sh).
-
----
-
-## Troubleshooting
-
-### "Access denied for user 'ulabelbase'@'localhost'"
-**Cause**: Old `config.ini` with wrong password from previous build
-
-**Solution**:
-```bash
-docker-compose exec labelbase_django bash
-rm /app/config.ini
-python manage.py make_config
-exit
-docker-compose restart labelbase_django
-```
-
-### "MYSQL_PASSWORD variable is not set" warnings
-**Cause**: Forgot to source exports.sh
-
-**Solution**: Always use `source exports.sh && docker-compose up`
-
-These warnings appear at parse time but are harmless if you source exports.sh before running the command.
-
-### "Can't connect to MySQL server"
-**Cause**: Database not ready yet
-
-**Solution**: Wait 15 seconds (run.sh has a built-in delay) or check logs:
-```bash
-docker-compose logs labelbase_mysql
-```
-
-### Django can't see code changes
-- Volume mounting issue
-- Solution: `docker-compose restart labelbase_django`
-- Or use `--reload` in gunicorn (already enabled)
-
-### Port 8080 already in use
-```bash
-# Find what's using it
-lsof -i :8080
-
-# Change port in docker-compose.yml
-ports:
- - "127.0.0.1:8081:8080" # Use 8081 instead
-```
-
----
-
-## Development Tips
-
-### Live Code Reloading
-Django container mounts `./django:/app`, so changes are live. Gunicorn runs with `--reload` flag.
-
-### Static Files
-After changing CSS/JS:
-```bash
-docker-compose exec labelbase_django python manage.py collectstatic --noinput
-```
-
-### Running Tests
-```bash
-docker-compose exec labelbase_django python manage.py test
-```
-
-### Python Dependencies
-Add to `requirements.txt`, then:
-```bash
-docker-compose up --build -d
-```
-
-### Database Migrations
-```bash
-# Create migrations
-docker-compose exec labelbase_django python manage.py makemigrations
-
-# Apply migrations
-docker-compose exec labelbase_django python manage.py migrate
-
-# Show migration status
-docker-compose exec labelbase_django python manage.py showmigrations
-```
-
----
-
-## Production Deployment
-
-See main README for:
-- Setting up nginx reverse proxy
-- SSL certificates with certbot
-- Domain configuration
-- Firewall rules
-
----
-
-## Quick Command Reference
-
-```bash
-# Build and run
-source exports.sh && ./build-and-run-labelbase.sh
-
-# Stop
-docker-compose down
-
-# Logs
-docker-compose logs -f
-
-# Shell
-docker-compose exec labelbase_django bash
-or
-source exports.sh && docker-compose exec labelbase_django bash
-
-# Reset everything (DANGER: deletes data!)
-docker-compose down -v && source exports.sh && docker-compose up --build -d
-
-# Reset config.ini (if passwords changed)
-docker-compose exec labelbase_django rm /app/config.ini
-docker-compose restart labelbase_django
-```
-
----
-
-## Environment Variables Reference
-
-| Variable | Purpose | Default |
-|----------|---------|---------|
-| `MYSQL_ROOT_PASSWORD` | MySQL root password | (required) |
-| `MYSQL_PASSWORD` | MySQL user password | (required) |
-| `MYSQL_DATABASE` | Database name | `labelbase` |
-| `MYSQL_USER` | Database user | `ulabelbase` |
-| `MYSQL_HOST` | Database hostname | `labelbase_mysql` |
-| `MYSQL_PORT` | Database port | `3306` |
-
-Set in `exports.sh` file and load with `source exports.sh` before running docker-compose.
-
-**Note I**: You may see warnings like "variable is not set" when running docker-compose commands. These appear at parse time but are harmless - the variables are properly set when you source exports.sh before the command.
-
-**NOTE II**: Some of the variables are hard coded or may need to be changed in the docker-compose.yml file manually.
-
----
-
-## Need Help?
-
-- Check logs: `docker-compose logs -f`
-- View settings: `docker-compose exec labelbase_django cat /app/config.ini`
-- Check environment: `docker-compose exec labelbase_django env | grep MYSQL`
-- Access Django shell: `docker-compose exec labelbase_django python manage.py shell`
diff --git a/build-and-run-labelbase.sh b/build-and-run-labelbase.sh
index b00af70..bfa4ec3 100755
--- a/build-and-run-labelbase.sh
+++ b/build-and-run-labelbase.sh
@@ -7,19 +7,17 @@ docker-compose down # make sure Labelbase is terminated.
if [[ -z "${MYSQL_ROOT_PASSWORD}" ]]; then
echo "Error: MYSQL_ROOT_PASSWORD environment variable is not set"
exit 1
+else
+ export $MYSQL_ROOT_PASSWORD
fi
if [[ -z "${MYSQL_PASSWORD}" ]]; then
echo "Error: MYSQL_PASSWORD environment variable is not set"
exit 1
+else
+ export $MYSQL_PASSWORD
fi
-
-export MYSQL_ROOT_PASSWORD
-export MYSQL_PASSWORD
-
-
-
# Check git next
LAST_GIT_COMMIT_FILE=".last_git_commit"
diff --git a/django/Dockerfile b/django/Dockerfile
index 5b90b98..2e9f23c 100644
--- a/django/Dockerfile
+++ b/django/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.11
+FROM python:3.9
ENV PYTHONUNBUFFERED 1
@@ -11,7 +11,7 @@ RUN apt-get update && \
default-libmysqlclient-dev \
build-essential \
cron vim logrotate \
- libpcre2-dev \
+ libpcre3-dev \
default-mysql-client \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --upgrade pip \
diff --git a/django/attachments/README.md b/django/attachments/README.md
deleted file mode 100644
index e9cd013..0000000
--- a/django/attachments/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Friendly-forked at commit https://github.com/bartTC/django-attachments/commit/d0c3e7b366691f2a57329c804b0dd002064396a6 (version 1.11) and extended for Labelbase.
-
-BSD 3-Clause "New" or "Revised" License https://github.com/bartTC/django-attachments/blob/master/LICENSE
diff --git a/django/attachments/__init__.py b/django/attachments/__init__.py
deleted file mode 100644
index 0bb3635..0000000
--- a/django/attachments/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-default_app_config = "attachments.apps.AttachmentsConfig"
diff --git a/django/attachments/admin.py b/django/attachments/admin.py
deleted file mode 100644
index 9e9672d..0000000
--- a/django/attachments/admin.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from __future__ import unicode_literals
-
-from django.contrib.contenttypes.admin import GenericStackedInline
-
-
-
-from django.contrib import admin
-from .models import LabelAttachment
-from .models import Attachment
-
-class AttachmentInlines(GenericStackedInline):
- model = Attachment
- exclude = ()
- extra = 1
-
-
-
-
-
-
-admin.site.register(LabelAttachment)
diff --git a/django/attachments/apps.py b/django/attachments/apps.py
deleted file mode 100644
index 1c56402..0000000
--- a/django/attachments/apps.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from __future__ import unicode_literals
-from django.apps import AppConfig
-from django.utils.translation import gettext_lazy as _
-
-
-class AttachmentsConfig(AppConfig):
- default_auto_field = 'django.db.models.AutoField'
- name = "attachments"
- verbose_name = _("Attachments")
diff --git a/django/attachments/forms.py b/django/attachments/forms.py
deleted file mode 100644
index a2d84e7..0000000
--- a/django/attachments/forms.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from __future__ import unicode_literals
-
-from django import forms
-from django.apps import apps
-from django.conf import settings
-from django.contrib.contenttypes.models import ContentType
-from django.template.defaultfilters import filesizeformat
-from django.utils.translation import gettext_lazy as _
-
-from .models import Attachment
-
-config = apps.get_app_config("attachments")
-
-
-def validate_max_size(data):
- if (
- hasattr(settings, "FILE_UPLOAD_MAX_SIZE")
- and data.size > settings.FILE_UPLOAD_MAX_SIZE
- ):
- raise forms.ValidationError(
- _("File exceeds maximum size of {size}").format(
- size=filesizeformat(settings.FILE_UPLOAD_MAX_SIZE)
- )
- )
-
-
-def custom_attachment_validators(uploaded_file):
- for validator in getattr(config, "attachment_validators", ()):
- validator(uploaded_file)
-
-
-class AttachmentForm(forms.ModelForm):
- attachment_file = forms.FileField(
- label=_("Upload attachment"),
- validators=[validate_max_size, custom_attachment_validators]
- )
-
- class Meta:
- model = Attachment
- fields = ("attachment_file",)
-
- def save(self, request, obj, *args, **kwargs):
- self.instance.creator = request.user
- self.instance.content_type = ContentType.objects.get_for_model(obj)
- self.instance.object_id = obj.pk
- super(AttachmentForm, self).save(*args, **kwargs)
diff --git a/django/attachments/locale/README.transifex b/django/attachments/locale/README.transifex
deleted file mode 100644
index 90789a0..0000000
--- a/django/attachments/locale/README.transifex
+++ /dev/null
@@ -1,11 +0,0 @@
-Transifex.net Token Verification
-=================================
-
-The list of tokens bellow guarantee the respective users to be able to enable
-submission on components using the following repository url:
-
-git://github.com/bartTC/django-attachments.git
-
-Tokens:
-
-24u9WhbEZQbCGDn3ruWJ6c7YpDpSxCCf / bartTC
diff --git a/django/attachments/locale/da/LC_MESSAGES/django.mo b/django/attachments/locale/da/LC_MESSAGES/django.mo
deleted file mode 100644
index cae96e1..0000000
Binary files a/django/attachments/locale/da/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/django/attachments/locale/da/LC_MESSAGES/django.po b/django/attachments/locale/da/LC_MESSAGES/django.po
deleted file mode 100644
index 8aeb328..0000000
--- a/django/attachments/locale/da/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,54 +0,0 @@
-# django-attachments in Danish
-# django-attachments på Dansk
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# Michael Lind Mortensen , 2009.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: django-attachments\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-08-27 23:52+0200\n"
-"PO-Revision-Date: 2010-04-04 00:24+0100\n"
-"Last-Translator: Martin Mahner \n"
-"Language-Team: da \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:7
-msgid "Upload attachment"
-msgstr "Upload vedhæftning"
-
-#: models.py:29
-msgid "creator"
-msgstr "skaber"
-
-#: models.py:30
-msgid "attachment"
-msgstr "vedhæftning"
-
-#: models.py:31
-msgid "created"
-msgstr "skabt"
-
-#: models.py:32
-msgid "modified"
-msgstr "ændret"
-
-#: views.py:33
-msgid "Your attachment was uploaded."
-msgstr "Din vedhæftning blev uploadet."
-
-#: views.py:51
-msgid "Your attachment was deleted."
-msgstr "Din vedhæftning blev slettet."
-
-#: templates/attachments/add_form.html:7
-msgid "Add attachment"
-msgstr "Tilføj vedhæftning"
-
-#: templates/attachments/delete_link.html:2
-msgid "Delete attachment"
-msgstr "Slet vedhæftning"
-
diff --git a/django/attachments/locale/de/LC_MESSAGES/django.mo b/django/attachments/locale/de/LC_MESSAGES/django.mo
deleted file mode 100644
index 979dd37..0000000
Binary files a/django/attachments/locale/de/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/django/attachments/locale/de/LC_MESSAGES/django.po b/django/attachments/locale/de/LC_MESSAGES/django.po
deleted file mode 100644
index 22a4f6d..0000000
--- a/django/attachments/locale/de/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,53 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: django-attachments\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-04-04 00:19+0200\n"
-"PO-Revision-Date: 2010-04-04 00:29+0100\n"
-"Last-Translator: Martin Mahner \n"
-"Language-Team: de \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:7
-msgid "Upload attachment"
-msgstr "Anhang hochladen"
-
-#: models.py:29
-msgid "creator"
-msgstr "Autor"
-
-#: models.py:30
-msgid "attachment"
-msgstr "Anhang"
-
-#: models.py:31
-msgid "created"
-msgstr "Erstellt"
-
-#: models.py:32
-msgid "modified"
-msgstr "Geändert"
-
-#: views.py:33
-msgid "Your attachment was uploaded."
-msgstr "Dein Anhang wurde hochgeladen."
-
-#: views.py:51
-msgid "Your attachment was deleted."
-msgstr "Dein Anhang wurde gelöscht."
-
-#: templates/attachments/add_form.html:7
-msgid "Add attachment"
-msgstr "Anhang hinzufügen"
-
-#: templates/attachments/delete_link.html:2
-msgid "Delete attachment"
-msgstr "Anhang löschen"
-
diff --git a/django/attachments/locale/el/LC_MESSAGES/django.mo b/django/attachments/locale/el/LC_MESSAGES/django.mo
deleted file mode 100644
index ce0354e..0000000
Binary files a/django/attachments/locale/el/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/django/attachments/locale/el/LC_MESSAGES/django.po b/django/attachments/locale/el/LC_MESSAGES/django.po
deleted file mode 100644
index e2452ee..0000000
--- a/django/attachments/locale/el/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,53 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# Panos Laganakos , 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: django-attachments\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-04-04 02:52+0300\n"
-"PO-Revision-Date: 2010-04-04 15:07+0100\n"
-"Last-Translator: Martin Mahner \n"
-"Language-Team: gr \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:7
-msgid "Upload attachment"
-msgstr "Φώρτωση συνημμένου"
-
-#: models.py:29
-msgid "creator"
-msgstr "δημιουργός"
-
-#: models.py:30
-msgid "attachment"
-msgstr "συνημμένο"
-
-#: models.py:31
-msgid "created"
-msgstr "δημιουργήθηκε"
-
-#: models.py:32
-msgid "modified"
-msgstr "τροποποιήθηκε"
-
-#: views.py:33
-msgid "Your attachment was uploaded."
-msgstr "Η επισύναψη σας ολοκληρώθηκε."
-
-#: views.py:51
-msgid "Your attachment was deleted."
-msgstr "Το συνήμμενο σας διεγράφει."
-
-#: templates/attachments/add_form.html:7
-msgid "Add attachment"
-msgstr "Προσθέστε ένα συνημμένο"
-
-#: templates/attachments/delete_link.html:2
-msgid "Delete attachment"
-msgstr "Διαγράψτε το συνημμένο"
-
diff --git a/django/attachments/locale/en/LC_MESSAGES/django.mo b/django/attachments/locale/en/LC_MESSAGES/django.mo
deleted file mode 100644
index d50a24a..0000000
Binary files a/django/attachments/locale/en/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/django/attachments/locale/en/LC_MESSAGES/django.po b/django/attachments/locale/en/LC_MESSAGES/django.po
deleted file mode 100644
index 0b3ccbc..0000000
--- a/django/attachments/locale/en/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,53 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: django-attachments\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-04-04 00:18+0200\n"
-"PO-Revision-Date: 2010-04-04 00:28+0100\n"
-"Last-Translator: Martin Mahner \n"
-"Language-Team: en \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:7
-msgid "Upload attachment"
-msgstr "Upload attachment"
-
-#: models.py:29
-msgid "creator"
-msgstr "creator"
-
-#: models.py:30
-msgid "attachment"
-msgstr "attachment"
-
-#: models.py:31
-msgid "created"
-msgstr "created"
-
-#: models.py:32
-msgid "modified"
-msgstr "modified"
-
-#: views.py:33
-msgid "Your attachment was uploaded."
-msgstr "Your attachment was uploaded."
-
-#: views.py:51
-msgid "Your attachment was deleted."
-msgstr "Your attachment was deleted."
-
-#: templates/attachments/add_form.html:7
-msgid "Add attachment"
-msgstr "Add attachment"
-
-#: templates/attachments/delete_link.html:2
-msgid "Delete attachment"
-msgstr "Delete attachment"
-
diff --git a/django/attachments/locale/es_AR/LC_MESSAGES/django.mo b/django/attachments/locale/es_AR/LC_MESSAGES/django.mo
deleted file mode 100644
index a8b2ce5..0000000
Binary files a/django/attachments/locale/es_AR/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/django/attachments/locale/es_AR/LC_MESSAGES/django.po b/django/attachments/locale/es_AR/LC_MESSAGES/django.po
deleted file mode 100644
index 12ec32e..0000000
--- a/django/attachments/locale/es_AR/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,54 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# Gonzalo Bustos, 2015.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-05-31 17:20-0300\n"
-"PO-Revision-Date: 2015-10-11 22:04-0300\n"
-"Last-Translator: Gonzalo Bustos\n"
-"Language-Team: Spanish (Argentina)\n"
-"Language: es_AR\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 1.6.10\n"
-
-#: forms.py:7
-msgid "Upload attachment"
-msgstr "Subir adjunto"
-
-#: models.py:32
-msgid "creator"
-msgstr "creador"
-
-#: models.py:33 models.py:38
-msgid "attachment"
-msgstr "adjunto"
-
-#: models.py:34
-msgid "created"
-msgstr "creado"
-
-#: models.py:35
-msgid "modified"
-msgstr "modificado"
-
-#: views.py:34
-msgid "Your attachment was uploaded."
-msgstr "Su adjunto fue subido."
-
-#: views.py:52
-msgid "Your attachment was deleted."
-msgstr "Su adjunto fue eliminado."
-
-#: templates/attachments/add_form.html:7
-msgid "Add attachment"
-msgstr "Agregar adjunto"
-
-#: templates/attachments/delete_link.html:2
-msgid "Delete attachment"
-msgstr "Eliminar adjunto"
diff --git a/django/attachments/locale/fi/LC_MESSAGES/django.mo b/django/attachments/locale/fi/LC_MESSAGES/django.mo
deleted file mode 100644
index 599e3dd..0000000
Binary files a/django/attachments/locale/fi/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/django/attachments/locale/fi/LC_MESSAGES/django.po b/django/attachments/locale/fi/LC_MESSAGES/django.po
deleted file mode 100644
index a57f0de..0000000
--- a/django/attachments/locale/fi/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,54 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: django-attachments\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2016-06-11 15:55+0300\n"
-"PO-Revision-Date: 2016-06-11 16:03+0300\n"
-"Last-Translator: Aleksi Häkli \n"
-"Language-Team: fi \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Language: fi\n"
-"X-Generator: Poedit 1.8.8\n"
-
-#: forms.py:7
-msgid "Upload attachment"
-msgstr "Lähetä liite"
-
-#: models.py:29
-msgid "creator"
-msgstr "luoja"
-
-#: models.py:30
-msgid "attachment"
-msgstr "liite"
-
-#: models.py:31
-msgid "created"
-msgstr "luotu"
-
-#: models.py:32
-msgid "modified"
-msgstr "muokattu"
-
-#: views.py:33
-msgid "Your attachment was uploaded."
-msgstr "Liitteesi ladattiin palvelimelle."
-
-#: views.py:51
-msgid "Your attachment was deleted."
-msgstr "Liitteesi poistettiin palvelimelta."
-
-#: templates/attachments/add_form.html:7
-msgid "Add attachment"
-msgstr "Lisää liite"
-
-#: templates/attachments/delete_link.html:2
-msgid "Delete attachment"
-msgstr "Poista liite"
diff --git a/django/attachments/locale/fr/LC_MESSAGES/django.mo b/django/attachments/locale/fr/LC_MESSAGES/django.mo
deleted file mode 100644
index 22abd79..0000000
Binary files a/django/attachments/locale/fr/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/django/attachments/locale/fr/LC_MESSAGES/django.po b/django/attachments/locale/fr/LC_MESSAGES/django.po
deleted file mode 100644
index 4f71bc1..0000000
--- a/django/attachments/locale/fr/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,53 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: django-attachments\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-04-04 00:18+0200\n"
-"PO-Revision-Date: 2010-04-04 00:28+0100\n"
-"Last-Translator: AERT \n"
-"Language-Team: en \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:7
-msgid "Upload attachment"
-msgstr "Ajouter une pièce jointe"
-
-#: models.py:29
-msgid "creator"
-msgstr "créateur"
-
-#: models.py:30
-msgid "attachment"
-msgstr "pièce jointe"
-
-#: models.py:31
-msgid "created"
-msgstr "créé"
-
-#: models.py:32
-msgid "modified"
-msgstr "modifié"
-
-#: views.py:33
-msgid "Your attachment was uploaded."
-msgstr "Votre pièce jointe a été ajoutée."
-
-#: views.py:51
-msgid "Your attachment was deleted."
-msgstr "Votre pièce jointe a été supprimée."
-
-#: templates/attachments/add_form.html:7
-msgid "Add attachment"
-msgstr "Ajouter une pièce jointe"
-
-#: templates/attachments/delete_link.html:2
-msgid "Delete attachment"
-msgstr "Supprimer la pièce jointe"
-
diff --git a/django/attachments/locale/it/LC_MESSAGES/django.mo b/django/attachments/locale/it/LC_MESSAGES/django.mo
deleted file mode 100644
index a8b50f4..0000000
Binary files a/django/attachments/locale/it/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/django/attachments/locale/it/LC_MESSAGES/django.po b/django/attachments/locale/it/LC_MESSAGES/django.po
deleted file mode 100644
index b782b75..0000000
--- a/django/attachments/locale/it/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,77 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-05-29 16:35+0200\n"
-"PO-Revision-Date: 2018-05-29 16:39+0200\n"
-"Language: it\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"Last-Translator: Mario Orlandi \n"
-"Language-Team: \n"
-"X-Generator: Poedit 2.0.7\n"
-
-#: apps.py:7
-msgid "Attachments"
-msgstr "Allegati"
-
-#: forms.py:13
-#, python-brace-format
-msgid "File exceeds maximum size of {size}"
-msgstr "Il file supera le dimensioni massime di {size}"
-
-#: forms.py:18
-msgid "Upload attachment"
-msgstr "Invia allegato"
-
-#: models.py:35
-msgid "creator"
-msgstr "autore"
-
-#: models.py:36 models.py:41
-msgid "attachment"
-msgstr "allegato"
-
-#: models.py:37
-msgid "created"
-msgstr "creato il"
-
-#: models.py:38
-msgid "modified"
-msgstr "modificato il"
-
-#: models.py:42
-msgid "attachments"
-msgstr "allegati"
-
-#: models.py:45
-msgid "Can delete foreign attachments"
-msgstr "Può eliminare gli allegati esterni"
-
-#: models.py:49
-#, python-brace-format
-msgid "{username} attached {filename}"
-msgstr "{username} ha allegato {filename}"
-
-#: templates/attachments/add_form.html:8
-msgid "Add attachment"
-msgstr "Aggiungi allegato"
-
-#: templates/attachments/delete_link.html:2
-msgid "Delete attachment"
-msgstr "Elimina allegato"
-
-#: views.py:51
-msgid "Your attachment was uploaded."
-msgstr "L'allegato è stato inviato."
-
-#: views.py:75
-msgid "Your attachment was deleted."
-msgstr "L'allegato è stato eliminato."
diff --git a/django/attachments/locale/pt_BR/LC_MESSAGES/django.mo b/django/attachments/locale/pt_BR/LC_MESSAGES/django.mo
deleted file mode 100644
index f1b6105..0000000
Binary files a/django/attachments/locale/pt_BR/LC_MESSAGES/django.mo and /dev/null differ
diff --git a/django/attachments/locale/pt_BR/LC_MESSAGES/django.po b/django/attachments/locale/pt_BR/LC_MESSAGES/django.po
deleted file mode 100644
index 8b7af3a..0000000
--- a/django/attachments/locale/pt_BR/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,54 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-05-31 17:20-0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:7
-msgid "Upload attachment"
-msgstr "Subir anexo"
-
-#: models.py:32
-msgid "creator"
-msgstr "criador"
-
-#: models.py:33 models.py:38
-msgid "attachment"
-msgstr "anexo"
-
-#: models.py:34
-msgid "created"
-msgstr "criado"
-
-#: models.py:35
-msgid "modified"
-msgstr "modificado"
-
-#: views.py:34
-msgid "Your attachment was uploaded."
-msgstr "Seu anexo foi enviado."
-
-#: views.py:52
-msgid "Your attachment was deleted."
-msgstr "Seu anexo foi excluido."
-
-#: templates/attachments/add_form.html:7
-msgid "Add attachment"
-msgstr "Adicionar anexo"
-
-#: templates/attachments/delete_link.html:2
-msgid "Delete attachment"
-msgstr "Excluir anexo"
diff --git a/django/attachments/locale/ru/LC_MESSAGES/django.po b/django/attachments/locale/ru/LC_MESSAGES/django.po
deleted file mode 100644
index 5c74ab9..0000000
--- a/django/attachments/locale/ru/LC_MESSAGES/django.po
+++ /dev/null
@@ -1,61 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR , YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: django-attachments\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-04-07 23:35+0300\n"
-"PO-Revision-Date: 2017-04-07 23:26+0300:MI+ZONE\n"
-"Last-Translator: Maxim Barabanov \n"
-"Language-Team: RU \n"
-"Language: Russian\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
-"%100>=11 && n%100<=14)? 2 : 3);\n"
-
-#: .\attachments\forms.py:9
-msgid "Upload attachment"
-msgstr "Загрузить вложение"
-
-#: .\attachments\models.py:35
-msgid "creator"
-msgstr "создатель"
-
-#: .\attachments\models.py:36
-msgid "attachment"
-msgstr "вложение"
-
-#: .\attachments\models.py:37
-msgid "created"
-msgstr "создано"
-
-#: .\attachments\models.py:38
-msgid "modified"
-msgstr "изменено"
-
-#: .\attachments\models.py:43
-msgid "Can delete foreign attachments"
-msgstr "Может удалять чужие вложения"
-
-#: .\attachments\templates\attachments\add_form.html:8
-msgid "Add attachment"
-msgstr "Добавить вложение"
-
-#: .\attachments\templates\attachments\delete_link.html:2
-msgid "Delete attachment"
-msgstr "Удалить вложение"
-
-#: .\attachments\views.py:40
-msgid "Your attachment was uploaded."
-msgstr "Ваше вложение было загружено."
-
-#: .\attachments\views.py:62
-msgid "Your attachment was deleted."
-msgstr "Ваше вложение было удалено"
diff --git a/django/attachments/management/__init__.py b/django/attachments/management/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/django/attachments/management/commands/__init__.py b/django/attachments/management/commands/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/django/attachments/management/commands/delete_stale_attachments.py b/django/attachments/management/commands/delete_stale_attachments.py
deleted file mode 100644
index 7f77ad3..0000000
--- a/django/attachments/management/commands/delete_stale_attachments.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from django.core.management.base import BaseCommand
-
-from attachments.models import Attachment
-from attachments.views import remove_file_from_disk
-
-
-class Command(BaseCommand):
- help = ("Remove attachments for which the related objects "
- "don't exist anymore!")
-
- def add_arguments(self, parser):
- parser.add_argument(
- '-y', '--yes', default='x', action='store_const', const='y',
- dest='answer', help='Automatically confirm deletion',
- )
-
- def handle(self, *args, **kwargs):
- verbose = kwargs['verbosity'] >= 1
- answer = kwargs['answer']
-
- # -v0 sets --yes
- if not verbose:
- answer = 'y'
-
- for att in Attachment.objects.all():
- if att.content_object is None:
- if verbose:
- self.stdout.write(
- "Attachment `%s' to non-existing `%s' with PK `%s'" %
- (att, att.content_type.model, att.object_id))
-
- while answer not in 'yn':
- answer = input("Do you wish to delete? [yN] ")
- if not answer:
- answer = 'x'
- continue
- answer = answer[0].lower()
-
- if answer == 'y' :
- remove_file_from_disk(att.attachment_file)
- att.delete()
-
- if verbose:
- self.stdout.write("Deleted attachment `%s'" % att)
diff --git a/django/attachments/migrations/0001_initial.py b/django/attachments/migrations/0001_initial.py
deleted file mode 100644
index 3057bbd..0000000
--- a/django/attachments/migrations/0001_initial.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import models, migrations
-from django.conf import settings
-import attachments.models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('contenttypes', '0001_initial'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Attachment',
- fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('object_id', models.PositiveIntegerField()),
- ('attachment_file', models.FileField(upload_to=attachments.models.attachment_upload, verbose_name='attachment')),
- ('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
- ('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
- ('content_type', models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE)),
- ('creator', models.ForeignKey(related_name='created_attachments', verbose_name='creator', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
- ],
- options={
- 'ordering': ['-created'],
- 'permissions': (('delete_foreign_attachments', 'Can delete foreign attachments'),),
- },
- bases=(models.Model,),
- ),
- ]
diff --git a/django/attachments/migrations/0002_auto_20180104_1247.py b/django/attachments/migrations/0002_auto_20180104_1247.py
deleted file mode 100644
index 707fa43..0000000
--- a/django/attachments/migrations/0002_auto_20180104_1247.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11 on 2018-01-04 12:47
-from __future__ import unicode_literals
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('attachments', '0001_initial'),
- ]
-
- operations = [
- migrations.AlterModelOptions(
- name='attachment',
- options={'ordering': ['-created'], 'permissions': (('delete_foreign_attachments', 'Can delete foreign attachments'),), 'verbose_name': 'attachment', 'verbose_name_plural': 'attachments'},
- ),
- ]
diff --git a/django/attachments/migrations/0003_auto_20190722_1216.py b/django/attachments/migrations/0003_auto_20190722_1216.py
deleted file mode 100644
index a3820bd..0000000
--- a/django/attachments/migrations/0003_auto_20190722_1216.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.2.3 on 2019-07-22 12:16
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('attachments', '0002_auto_20180104_1247'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='attachment',
- name='object_id',
- field=models.TextField(),
- ),
- ]
diff --git a/django/attachments/migrations/0004_db_index.py b/django/attachments/migrations/0004_db_index.py
deleted file mode 100644
index 852a5b5..0000000
--- a/django/attachments/migrations/0004_db_index.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Generated by Django 3.0.9 on 2020-08-17 13:29
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('attachments', '0003_auto_20190722_1216'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='attachment',
- name='created',
- field=models.DateTimeField(auto_now_add=True, db_index=True,
- verbose_name='created'),
- ),
- migrations.AlterField(
- model_name='attachment',
- name='modified',
- field=models.DateTimeField(auto_now=True, db_index=True,
- verbose_name='modified'),
- ),
- ]
diff --git a/django/attachments/migrations/0005_object_id_charfield.py b/django/attachments/migrations/0005_object_id_charfield.py
deleted file mode 100644
index 303e51f..0000000
--- a/django/attachments/migrations/0005_object_id_charfield.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('attachments', '0004_db_index'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='attachment',
- name='object_id',
- field=models.CharField(db_index=True, max_length=64),
- ),
- ]
diff --git a/django/attachments/migrations/0006_alter_attachment_attachment_file.py b/django/attachments/migrations/0006_alter_attachment_attachment_file.py
deleted file mode 100644
index 7a398af..0000000
--- a/django/attachments/migrations/0006_alter_attachment_attachment_file.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 3.2.25 on 2024-04-11 14:35
-
-from django.db import migrations, models
-import uuid_upload_path.storage
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('attachments', '0005_object_id_charfield'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='attachment',
- name='attachment_file',
- field=models.FileField(upload_to=uuid_upload_path.storage.upload_to, verbose_name='attachment'),
- ),
- ]
diff --git a/django/attachments/migrations/0007_labelattachment.py b/django/attachments/migrations/0007_labelattachment.py
deleted file mode 100644
index 326e73f..0000000
--- a/django/attachments/migrations/0007_labelattachment.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Generated by Django 3.2.25 on 2024-04-11 15:06
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('attachments', '0006_alter_attachment_attachment_file'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='LabelAttachment',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('type_ref_hash', models.CharField(blank=True, help_text='Reflects type + ref, where type could be any bip-329 defined type', max_length=64)),
- ('network', models.CharField(choices=[('mainnet', 'Mainnet'), ('testnet', 'Testnet')], default='mainnet', help_text="Choose the network for this labelbase's label attachement.", max_length=10)),
- ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
- ],
- options={
- 'unique_together': {('user', 'network', 'type_ref_hash')},
- },
- ),
- ]
diff --git a/django/attachments/migrations/__init__.py b/django/attachments/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/django/attachments/models.py b/django/attachments/models.py
deleted file mode 100644
index 2f107cf..0000000
--- a/django/attachments/models.py
+++ /dev/null
@@ -1,111 +0,0 @@
-from __future__ import unicode_literals
-
-import os
-
-from django.conf import settings
-from django.contrib.contenttypes.fields import GenericForeignKey
-from django.contrib.contenttypes.models import ContentType
-from django.db import models
-from django.utils.translation import gettext_lazy as _
-from six import python_2_unicode_compatible
-from uuid_upload_path import upload_to
-from django.contrib.auth.models import User
-import logging
-
-logger = logging.getLogger('labelbase')
-
-def attachment_upload(instance, filename):
- pass # used for compatibility / migrations only.
-
-
-class AttachmentManager(models.Manager):
- def attachments_for_object(self, obj):
- object_type = ContentType.objects.get_for_model(obj)
- return self.filter(content_type__pk=object_type.id, object_id=obj.pk)
-
-
-@python_2_unicode_compatible
-class Attachment(models.Model):
- objects = AttachmentManager()
-
-
- content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
- object_id = models.CharField(db_index=True, max_length=64)
- content_object = GenericForeignKey("content_type", "object_id")
- creator = models.ForeignKey(
- settings.AUTH_USER_MODEL,
- related_name="created_attachments",
- verbose_name=_("creator"),
- on_delete=models.CASCADE,
- )
- attachment_file = models.FileField(
- _("attachment"), upload_to=upload_to
- )
- created = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
- modified = models.DateTimeField(_("modified"), auto_now=True, db_index=True)
-
- class Meta:
- verbose_name = _("attachment")
- verbose_name_plural = _("attachments")
- ordering = ["-created"]
- permissions = (
- ("delete_foreign_attachments", _("Can delete foreign attachments")),
- )
-
- def __str__(self):
- return _("{username} attached {filename}").format(
- username=self.creator.get_username(),
- filename=self.attachment_file.name,
- )
-
- @property
- def filename(self):
- return os.path.split(self.attachment_file.name)[1]
-
- def attach_to(self, new_object, update_path=False):
- """
- Attach to a new object and possibly move the actual file on disk!
-
- .. important::
-
- As long as path names are valid you can continue serving
- the files from their original path and not change it!
- """
- self.object_id = new_object.pk
- self.content_type = ContentType.objects.get_for_model(new_object)
- self.save()
-
- if update_path:
- old_path = self.attachment_file.path
- self.attachment_file.name = upload_to
- self.attachment_file.save()
-
- os.makedirs(
- os.path.dirname(self.attachment_file.path), exist_ok=True)
- os.rename(old_path, self.attachment_file.path)
-
-
-class LabelAttachment(models.Model):
-
- user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
- type_ref_hash = models.CharField(max_length=64,
- blank=True,
- help_text="Reflects type + ref, where type could be any bip-329 defined type")
-
- MAINNET = 'mainnet'
- TESTNET = 'testnet'
-
- NETWORK_CHOICES = [
- (MAINNET, 'Mainnet'),
- (TESTNET, 'Testnet'),
- ]
-
- network = models.CharField(
- max_length=10,
- choices=NETWORK_CHOICES,
- default='mainnet',
- help_text="Choose the network for this labelbase's label attachement."
- )
-
- class Meta:
- unique_together = (("user", "network", "type_ref_hash"),)
diff --git a/django/attachments/templatetags/__init__.py b/django/attachments/templatetags/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/django/attachments/templatetags/attachments_tags.py b/django/attachments/templatetags/attachments_tags.py
deleted file mode 100644
index 6580fef..0000000
--- a/django/attachments/templatetags/attachments_tags.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from django.template import Library
-from django.urls import reverse
-
-from ..forms import AttachmentForm
-from ..models import Attachment
-from ..views import add_url_for_obj
-
-register = Library()
-
-@register.filter(name='endswith')
-def endswith(value, arg):
- """Checks if the value ends with a certain string."""
- if isinstance(value, str):
- return value.endswith(arg)
- return False
-
-@register.inclusion_tag("attachments/add_form.html", takes_context=True)
-def attachment_form(context, obj, **kwargs):
- """
- Renders a "upload attachment" form.
-
- The user must own ``attachments.add_attachment permission`` to add
- attachments.
- """
-
- return {
- "form": AttachmentForm(),
- "form_url": add_url_for_obj(obj),
- "next": context.request.path,
- }
-
-
-@register.inclusion_tag("attachments/delete_link.html", takes_context=True)
-def attachment_delete_link(context, attachment, **kwargs):
- if context["user"] == attachment.creator:
- return {
- "next": context.request.path,
- "delete_url": reverse(
- "attachments:delete", kwargs={"attachment_pk": attachment.pk}
- ),
- }
-
-
-
-@register.simple_tag
-def attachments_count(obj):
- """
- Counts attachments that are attached to a given object::
-
- {% attachments_count obj %}
- """
- attachments_count = Attachment.objects.attachments_for_object(obj).count()
- print("obj: {}, attachments_count: {}".format(obj.id, attachments_count))
-
- return attachments_count
-
-
-@register.simple_tag
-def get_attachments_for(obj, *args, **kwargs):
- """
- Resolves attachments that are attached to a given object. You can specify
- the variable name in the context the attachments are stored using the `as`
- argument.
-
- Syntax::
-
- {% get_attachments_for obj as "my_attachments" %}
- """
- return Attachment.objects.attachments_for_object(obj)
diff --git a/django/attachments/tests/__init__.py b/django/attachments/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/django/attachments/tests/base.py b/django/attachments/tests/base.py
deleted file mode 100644
index 32d6945..0000000
--- a/django/attachments/tests/base.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- encoding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.contrib.auth.models import Permission, User
-from django.contrib.contenttypes.models import ContentType
-from django.core.files.uploadedfile import SimpleUploadedFile
-from django.test import TestCase
-from django.urls import reverse
-
-from ..models import Attachment
-from .testapp.models import TestModel
-
-
-class BaseTestCase(TestCase):
- target_model_class = TestModel
-
- def setUp(self):
- """
- Create two users with `attachments.add_attachment` permission
- and one object to attach files to.
- """
- content_type = ContentType.objects.get_for_model(Attachment)
- self.add_permission = Permission.objects.get(
- content_type=content_type, codename="add_attachment"
- )
- self.del_permission = Permission.objects.get(
- content_type=content_type, codename="delete_attachment"
- )
-
- self.del_foreign_permission = Permission.objects.get(
- content_type=content_type, codename="delete_foreign_attachments"
- )
-
- self.cred_jon = {"username": "jon", "password": "foobar"}
- self.cred_jane = {"username": "jane", "password": "foobar"}
- self.jon = User.objects.create_user(**self.cred_jon)
- self.jon.user_permissions.add(self.add_permission)
- self.jon.user_permissions.add(self.del_permission)
-
- self.jane = User.objects.create_user(**self.cred_jane)
- self.jane.user_permissions.add(self.add_permission)
- self.jane.user_permissions.add(self.del_permission)
-
- self.obj = self.target_model_class.objects.create(title="My first test item")
-
- def _upload_testfile(self, file_obj=None, file_content=b"file content", **extra):
- """
- Uploads a sample file for the given user.
- """
- add_url = reverse(
- "attachments:add",
- kwargs={
- "app_label": "testapp",
- "model_name": self.target_model_class.__name__.lower(),
- "pk": self.obj.pk,
- },
- )
-
- if not file_obj:
- file_obj = SimpleUploadedFile(
- "Ünicode Filename 🙂.jpg",
- file_content,
- content_type="image/jpeg",
- )
- return self.client.post(
- add_url, {"attachment_file": file_obj}, follow=True,
- **extra
- )
diff --git a/django/attachments/tests/test_integrity.py b/django/attachments/tests/test_integrity.py
deleted file mode 100644
index 68ac80c..0000000
--- a/django/attachments/tests/test_integrity.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from django.core.management import call_command
-from django.test import TestCase
-from six import StringIO
-
-
-class IntegrityTestCase(TestCase):
- """
- Very basic tests around the app itself, not the code.
- """
-
- def test_no_pending_migrations(self):
- """
- Make sure all model changes are reflected with Django migrations.
- """
- output = StringIO()
- call_command(
- "makemigrations", "--dry-run", interactive=False, stdout=output
- )
- self.assertTrue("No changes detected" in output.getvalue())
diff --git a/django/attachments/tests/test_template.py b/django/attachments/tests/test_template.py
deleted file mode 100644
index 1cac854..0000000
--- a/django/attachments/tests/test_template.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from django.urls import reverse
-
-from ..models import Attachment
-from .base import BaseTestCase
-
-
-class ViewTestCase(BaseTestCase):
- def setUp(self):
- super(ViewTestCase, self).setUp()
- self.item_url = reverse("testapp-detail", kwargs={"pk": self.obj.pk})
-
- def test_uploaded_attachment_urls_are_listed(self):
- self.client.login(**self.cred_jon)
- self._upload_testfile()
- response = self.client.get(self.item_url)
- attachment = Attachment.objects.attachments_for_object(self.obj)[0]
- self.assertTrue(attachment.attachment_file.url in str(response.content))
-
- def test_attachment_count_is_listed(self):
- self.client.login(**self.cred_jon)
- self._upload_testfile()
- self._upload_testfile()
- response = self.client.get(self.item_url)
- attachment_count = Attachment.objects.attachments_for_object(
- self.obj
- ).count()
- self.assertTrue(
- "Object has %d attachments" % attachment_count
- in str(response.content)
- )
-
- def test_upload_form_is_listed_with_add_permission(self):
- self.client.login(**self.cred_jon)
- response = self.client.get(self.item_url)
- self.assertTrue("
{% endblock %}
diff --git a/django/templates/encryption.html b/django/templates/encryption.html
index c925c51..be512f6 100644
--- a/django/templates/encryption.html
+++ b/django/templates/encryption.html
@@ -5,7 +5,7 @@
{% block nav_home %}active{% endblock %}
{% block content %}
-
+
L belbase
All your labels in one place.
@@ -18,8 +18,11 @@
-
-
+
+
Your labels are encrypted.
diff --git a/django/templates/fill_missing_data.html b/django/templates/fill_missing_data.html
deleted file mode 100644
index b1d3f93..0000000
--- a/django/templates/fill_missing_data.html
+++ /dev/null
@@ -1,128 +0,0 @@
-{% extends "_base.html" %}
-{% load i18n %}
-{% load labelbase_tags %}
-{% load sekizai_tags %}
-
-{% block content %}
-
-{% if labelbase %}
-
-
-
Fill Missing Data - {{ labelbase.name }}
- {% if labelbase.fingerprint %}
{{ labelbase.fingerprint }} {% endif %}
- {% if labelbase.about %}
-
{{ labelbase.about }}
- {% endif %}
-
-
-
-
-
- Auto-populate BIP-329 additional fields (height, time, value) for outputs from OutputStat data.
-
-
- {% if can_fill_from_outputstat %}
-
- {{ can_fill_from_outputstat|length }} output labels can be filled!
-
- {% else %}
-
- All good! All {{ already_complete|length }} output labels have their fields populated.
-
- {% endif %}
-
-
- {% if can_fill_from_outputstat %}
-
-
-
-
-
- {% for item in can_fill_from_outputstat %}
-
-
-
- {{ item.label.get_type_display }} : {{ item.label.ref }}
- Label: {{ item.label.label|default:"(empty)" }}
- Missing fields:
- {{ item.missing_fields|join:", " }}
- Available data:
-
- height={{ item.output_stat.confirmed_at_block_height }},
- time={{ item.output_stat.confirmed_at_block_time }},
- value={{ item.output_stat.value }} sats
-
-
-
-
- {% csrf_token %}
-
-
- Fill Now
-
-
Edit
-
-
-
- {% endfor %}
-
-
- {% csrf_token %}
-
- Fill All from OutputStat
-
-
-
-
- {% endif %}
-
-
- {% if already_complete %}
-
-
-
-
-
- {% for label in already_complete %}
-
-
-
- {{ label.get_type_display }} : {{ label.ref }}
- {{ label.label }}
-
-
-
-
- {% endfor %}
-
-
-
-
- {% endif %}
-
-
-
-{% else %}
-
Labelbase not found.
-{% endif %}
-
-
-
-{% endblock %}
diff --git a/django/templates/home.html b/django/templates/home.html
index e49663e..b69639f 100644
--- a/django/templates/home.html
+++ b/django/templates/home.html
@@ -268,11 +268,27 @@
-
Attachments
-
Enhance your labels with attachments. Attach images and files directly to your labels, streamlining your documentation workflow and enriching the data associated with each label.
+
Cloud-Hosted
+
Experience the flexibility and accessibility of Labelbase's cloud-hosted solution, providing users with a reliable and efficient label management service hosted on secure and scalable cloud infrastructure.
-
+ {% comment %}
+
+
+
Customized Email Sending
+
Personalize email sending with custom server settings. Labelbase enables users to configure preferred SMTP and IMAP options, enhancing control, privacy and security.
+
+
+ {% endcomment %}
+
+
+
+
On-demand Support Chat
+
We are happy to assist you with the integrated on-demand support chat by Chatwoot.
+ Privacy is ensured as the chat script loads only when you request it, which is totally optional.
+
+
+
Automated Output Management
@@ -287,47 +303,6 @@
-
-
-
BIP-329 Extended Fields
-
Full support for BIP-329 additional fields including transaction height, timestamp, fees, values, exchange rates, and derivation paths. Enrich your labels with comprehensive transaction metadata.
-
-
-
-
-
-
Currency Sync Tool
-
Seamlessly synchronize between legacy currency annotations in label text and structured FMV fields. Automatically detect and resolve conflicts to maintain data consistency across formats.
-
-
-
-
-
-
Bulk Auto-Fill Missing Data
-
Automatically bulk populate transaction metadata from OutputStat records. Fill block heights, timestamps, and values for your outputs with one click, saving time and ensuring accuracy.
-
-
-
-
-
-
One-Click BIP-329 Field Population
-
Automatically populate transaction metadata like block height, timestamp, and value directly from OutputStat records. View missing fields at a glance and fill them with a single click from the output details page.
-
-
-
-
-
-
-
Fee Health Monitoring
-
Instantly visualize the cost-effectiveness of spending your UTXOs. Labelbase calculates and displays fee health status for each spendable output, color-coded to show if transaction fees would consume a healthy percentage of the output value.
-
-
-
-
-
UTXO Fee Efficiency Visualization
-
Visualize the cost-effectiveness of spending your UTXOs with color-coded treemaps. See at a glance which outputs have healthy fee-to-value ratios and which would be expensive to spend, helping you make informed decisions about consolidation and transaction planning.
-
-
diff --git a/django/templates/knowledge_base/article_detail.html b/django/templates/knowledge_base/article_detail.html
index 8f660bc..4ae51cc 100644
--- a/django/templates/knowledge_base/article_detail.html
+++ b/django/templates/knowledge_base/article_detail.html
@@ -53,8 +53,7 @@ code {
{% endfor %}
-
{{ article.content | markdown | safe }}
-
+
{% endblock %}
diff --git a/django/templates/knowledge_base/category_detail.html b/django/templates/knowledge_base/category_detail.html
index 3618cc4..febd64f 100644
--- a/django/templates/knowledge_base/category_detail.html
+++ b/django/templates/knowledge_base/category_detail.html
@@ -8,7 +8,8 @@
{#% block nav_home %}active{% endblock %#}
{% block content %}
-
+
+
{% breadcrumbs_category category as current_breadcrumbs %}
@@ -30,5 +31,5 @@
{{ article.title }}
{% endfor %}
-
+
{% endblock %}
diff --git a/django/templates/knowledge_base/index.html b/django/templates/knowledge_base/index.html
index 5dc6140..2cedc8e 100644
--- a/django/templates/knowledge_base/index.html
+++ b/django/templates/knowledge_base/index.html
@@ -5,7 +5,7 @@
{% load static %}
{% load sekizai_tags %}
{% block title %}Knowledge Base Categories | Labelbase{% endblock %}
-
+{#% block nav_home %}active{% endblock %#}
{% block content %}
diff --git a/django/templates/label_derive_addresses.html b/django/templates/label_derive_addresses.html
deleted file mode 100644
index 76be5ef..0000000
--- a/django/templates/label_derive_addresses.html
+++ /dev/null
@@ -1,171 +0,0 @@
-{% extends "label_edit.html" %}
-{% load bootstrap %}
-{% load sekizai_tags %}
-{% load i18n %}
-{% load labelbase_tags %}
-
-
-{% block label_edit_content %}
-
-{% include "_modal_add_label.html" %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{% endblock %}
diff --git a/django/templates/label_edit.html b/django/templates/label_edit.html
index 5830f6f..aa2f9fd 100644
--- a/django/templates/label_edit.html
+++ b/django/templates/label_edit.html
@@ -2,74 +2,76 @@
{% load i18n %}
{% load sekizai_tags %}
{% load labelbase_tags %}
-{% load backgroundtask_tags %}
-{% load attachments_tags %}
-
-{% block title %}{{ object.type }} {{ object.ref }}{% endblock %}
{% block content %}
-{% get_attachments_for object.get_label_attachment as my_attachments %}
+
+
+ Label Detail
+
-
-
@@ -88,51 +90,58 @@
Ouch! Got "{{ output.last_error.message }}{% if output.last_error.code %} (code {{ output.last_error.code }}){% endif %}" from Electrum.
{% endif %}
-
-{% is_label_id_in_queue object.id as is_in_queue %}
-{% if is_in_queue %}
-
- Output in queue! This output is currently in the processing queue. It will be checked shortly.
-
-{% else %}
{% if object.type == "output" %}
- {% if output.get_spent_status == "spent" %}
-
- Output spent! Blockchain records indicate that this output has been spent in another transaction.
-
- {% elif output.get_spent_status == "unspent" %}
-
- Output unspent! Blockchain records indicate that this output has not been spent yet.
-
- {% elif output.get_spent_status == "unconfirmed" %}
-
-
-
- Actions
-
-
+ {% switch output.get_spent_status %}
+ {% case "spent" %}
+
+ Output spent! Blockchain records indicate that this output has been spent in another transaction.
-
Output unconfirmed! Blockchain records indicate that this output has not been confirmed yet.
-
- {% else %}
-
- Unknown status: The status of this output could not be determined.
-
- {% endif %}
+ {% case "unspent" %}
+
+ Output unspent! Blockchain records indicate that this output has not been spent yet.
+
+ {% case "unconfirmed" %}
+
+
+
+ Actions
+
+
+
+
Output unconfirmed! Blockchain records indicate that this output has not been confirmed yet.
+
+ {% endswitch %}
{% endif %}
-{% endif %}
+
+
+
+{% comment %}
+{% if object.type == "addr" %}
+
+ Heads up! There are multiple transaction outputs sent to this address. For privacy reasons, do not reuse addresses.
+
+{% endif %}
+{% endcomment %}
{% block label_edit_content %}
{% endblock %}
+
+
+
+
+
+
+
+
+
-
+
-{% addtoblock "js" %}
-// BIP-329 field visibility based on type
-const fieldsByType = {
- 'tx': ['origin', 'height', 'time', 'fee', 'value', 'rate'],
- 'addr': ['origin', 'keypath', 'heights'],
- 'pubkey': ['origin', 'keypath'],
- 'input': ['origin', 'keypath', 'value', 'fmv', 'height', 'time'],
- 'output': ['origin', 'spendable', 'keypath', 'value', 'fmv', 'height', 'time'],
- 'xpub': ['origin']
-};
-
-function updateFieldVisibility() {
- const typeField = document.getElementById('id_type');
- if (!typeField) return;
-
- const selectedType = typeField.value;
- const allowedFields = fieldsByType[selectedType] || [];
-
- // All additional fields (excluding core fields: type, ref, label)
- const allAdditionalFields = ['origin', 'spendable', 'height', 'time', 'fee', 'value', 'rate', 'keypath', 'fmv', 'heights'];
-
- allAdditionalFields.forEach(fieldName => {
- const field = document.getElementById(`id_${fieldName}`);
- if (field) {
- // Find the parent form-group/control-group div
- const wrapper = field.closest('.form-group') || field.closest('.control-group') || field.closest('.mb-3') || field.parentElement.parentElement;
-
- if (wrapper) {
- if (allowedFields.includes(fieldName)) {
- wrapper.style.display = '';
- field.removeAttribute('disabled');
- } else {
- wrapper.style.display = 'none';
- field.setAttribute('disabled', 'disabled');
- field.value = ''; // Clear hidden fields
- }
- }
- }
- });
-}
-
-// Run on page load
-document.addEventListener('DOMContentLoaded', function() {
- updateFieldVisibility();
-
- // Run when type changes
- const typeField = document.getElementById('id_type');
- if (typeField) {
- typeField.addEventListener('change', updateFieldVisibility);
- }
-});
-
-// Validate fmv field (JSON object with currency codes)
-const fmvField = document.getElementById('id_fmv');
-if (fmvField) {
- fmvField.addEventListener('blur', function() {
- try {
- if (this.value && this.value.trim()) {
- const parsed = JSON.parse(this.value);
- if (typeof parsed !== 'object' || Array.isArray(parsed)) {
- alert('FMV must be a JSON object like {"USD": 1233.45}');
- return;
- }
- // Validate values are numeric (accept both numbers and numeric strings)
- for (const [key, value] of Object.entries(parsed)) {
- // Check if it's a number OR a numeric string
- const isNumeric = typeof value === 'number' ||
- (typeof value === 'string' && !isNaN(parseFloat(value)) && isFinite(value));
- if (!isNumeric) {
- alert(`FMV value for ${key} must be numeric (got: ${typeof value})`);
- return;
- }
- }
- }
- } catch (e) {
- alert('Invalid JSON format for fmv field');
- }
- });
-}
-
-// Validate rate field (JSON object with currency codes)
-const rateField = document.getElementById('id_rate');
-if (rateField) {
- rateField.addEventListener('blur', function() {
- try {
- if (this.value && this.value.trim()) {
- const parsed = JSON.parse(this.value);
- if (typeof parsed !== 'object' || Array.isArray(parsed)) {
- alert('Rate must be a JSON object like {"USD": 105620.00}');
- return;
- }
- // Validate values are numeric (accept both numbers and numeric strings)
- for (const [key, value] of Object.entries(parsed)) {
- // Check if it's a number OR a numeric string
- const isNumeric = typeof value === 'number' ||
- (typeof value === 'string' && !isNaN(parseFloat(value)) && isFinite(value));
- if (!isNumeric) {
- alert(`Rate value for ${key} must be numeric (got: ${typeof value})`);
- return;
- }
- }
- }
- } catch (e) {
- alert('Invalid JSON format for rate field');
- }
- });
-}
-
-// Validate heights field (JSON array of integers)
-const heightsField = document.getElementById('id_heights');
-if (heightsField) {
- heightsField.addEventListener('blur', function() {
- try {
- if (this.value && this.value.trim()) {
- const parsed = JSON.parse(this.value);
- if (!Array.isArray(parsed)) {
- alert('Heights must be a JSON array like [123456, 789012]');
- return;
- }
- // Validate all values are integers
- for (const height of parsed) {
- if (!Number.isInteger(height)) {
- alert('All heights must be integers');
- return;
- }
- }
- }
- } catch (e) {
- alert('Invalid JSON format for heights field');
- }
- });
-}
-
-// Validate integer fields (height, fee, value)
-const integerFields = ['id_height', 'id_fee', 'id_value'];
-integerFields.forEach(fieldId => {
- const field = document.getElementById(fieldId);
- if (field) {
- field.addEventListener('blur', function() {
- if (this.value && this.value.trim()) {
- if (!/^-?\d+$/.test(this.value.trim())) {
- alert(`${fieldId.replace('id_', '')} must be an integer`);
- }
- }
- });
- }
-});
-
-{% endaddtoblock %}
+
+
{% endblock %}
diff --git a/django/templates/label_edit_attachments.html b/django/templates/label_edit_attachments.html
deleted file mode 100644
index 6e1ee3e..0000000
--- a/django/templates/label_edit_attachments.html
+++ /dev/null
@@ -1,99 +0,0 @@
-{% extends "label_edit.html" %}
-{% load bootstrap %}
-{% load i18n %}
-{% load static %}
-{% load attachments_tags %}
-{% load sekizai_tags %}
-
-
-{% block extra_media %}
-
-
-{% endblock %}
-
-
-{% block label_edit_content %}
-
-
-
-
-{% if my_attachments %}
-
-
-
-
- #
- Attachment
- Action
-
-
-
- {% for attachment in my_attachments %}
-
- {{ attachment.id }}
-
- {% if attachment.attachment_file.url|lower|endswith:".pdf" %}
-
-
- {% else %}
-
-
-
-
- {% endif %}
-
- {% attachment_delete_link attachment %}
-
- {% empty %}
-
- No attachments found.
-
- {% endfor %}
-
-
-
-{% else %}
-
-
-Label has {% attachments_count object.get_label_attachment %} attachments.
-
-
-{% endif %}
-
-
-{% attachment_form form.instance %}
-
-
-
-{% endblock %}
diff --git a/django/templates/label_edit_labeling.html b/django/templates/label_edit_labeling.html
index 35416fc..034cbcc 100644
--- a/django/templates/label_edit_labeling.html
+++ b/django/templates/label_edit_labeling.html
@@ -168,8 +168,6 @@
+
+
+
{% endif %}
+
+
{% endblock %}
diff --git a/django/templates/labelbase_tree_maps.html b/django/templates/labelbase_tree_maps.html
index b18d500..36e80e1 100644
--- a/django/templates/labelbase_tree_maps.html
+++ b/django/templates/labelbase_tree_maps.html
@@ -16,12 +16,6 @@
Unspent Spendable Outputs
-
-
- Fee Efficiency (VTER)
-
-
diff --git a/django/templates/labelbase_tree_maps_unspent_outputs.html b/django/templates/labelbase_tree_maps_unspent_outputs.html
index 326ceb2..ceeb33a 100644
--- a/django/templates/labelbase_tree_maps_unspent_outputs.html
+++ b/django/templates/labelbase_tree_maps_unspent_outputs.html
@@ -11,26 +11,14 @@
-{% if action == "fee-efficiency" %}
-
-
-
-
Fee Efficiency (VTER): Color shows how efficient it is to spend each output based on the fee-to-value ratio.
-
- 🟢
Healthy: Fee < {{ request.user.profile.my_fee_threshold_healthy }}% of value
- 🟡
Warning: Fee {{ request.user.profile.my_fee_threshold_healthy }}-{{ request.user.profile.my_fee_threshold_warning }}% of value
- 🔴
High: Fee > {{ request.user.profile.my_fee_threshold_warning }}% of value
-
-
Box size represents output value in sats. Thresholds based on your fee settings. Adjust in Profile → Fees
-
-
-{% elif labelbase.is_testnet %}
+
+{% if labelbase.is_testnet %}
Important Notice for Testnet Transactions: Testnet coins hold no real-world value and are solely intended for testing purposes.
-
+
@@ -40,7 +28,6 @@ If the UTXO value is not provided, Labelbase will estimate it based on historica
{% endif %}
-