feat: add packet labeling functionality and awaiting text scope for seed photos

This commit is contained in:
Brenda Wallace committed 2026-10-28 20:44:37 +13:00
1 parent 062bf738b3
commit cd6f8f1876
8 files changed
+72 -12

No files matched your search

+6
View File
@@ -18,6 +18,12 @@ module PhotosHelper
{ photo_dialog: true, photo_label: photo_label(model) }
end
# The link that sets which side of a seed's packet a photo of it is of; an
# empty label says it is not of the packet.
def packet_label_path(seed, photo, label)
photo_association_path(photo_id: photo.id, type: 'seed', id: seed.id, label:)
end
def crop_image_path(crop)
thumbnail_url(crop.default_photo)
end
+3
View File
@@ -27,6 +27,9 @@ class PhotoAssociation < ApplicationRecord
# Photos of a seed packet say what the packet says, not what the crop looks
# like, so a photo labelled as one is left out of a crop's own photos.
scope :of_packets, -> { where(photographable_type: 'Seed').where.not(label: nil) }
# Those of a seed that has no text from its packet yet, which is what still
# needs reading.
scope :awaiting_text, -> { of_packets.where(photographable_id: Seed.unscoped.where(packet_text: [nil, '']).select(:id)) }
def self.item(item_id, item_type)
find_by!(photographable_id: item_id, photographable_type: item_type).photographable
+6
View File
@@ -60,6 +60,12 @@ class Seed < ApplicationRecord
photo_associations.find_by(label: side)&.photo
end
# Which side of the packet a photo of this seed is of, if it is of one. Uses
# the loaded photo associations, so a page of photos reads them once.
def label_of(photo)
photo_associations.find { |association| association.photo_id == photo.id }&.label
end
# The packet's front, if there is a photo of it. Otherwise the most liked
# photo that is not of the back.
def default_photo
+5 -7
View File
@@ -1,13 +1,11 @@
-# On a seed's own page: whether each of its photos is of the packet, and which
-# side. Everyone sees what it is; the seed's owner can change it.
- label = seed.photo_associations.find { |association| association.photo_id == photo.id }&.label
- label_path = ->(choice) { photo_association_path(photo_id: photo.id, type: 'seed', id: seed.id, label: choice) }
- label = seed.label_of(photo)
.packet-label.small.mt-1
- if label
%span.badge.text-bg-secondary= t("seeds.packet.label_#{label}")
- if can?(:update, seed)
- choices = PhotoAssociation::LABELS['Seed'].without(label)
- choices.each do |choice|
= link_to t("seeds.packet.mark_#{choice}"), label_path.call(choice), method: :patch, class: 'me-2'
- if label
= link_to t('seeds.packet.not_packet'), label_path.call(''), method: :patch, class: 'me-2'
- PhotoAssociation::LABELS['Seed'].without(label).each do |choice|
= link_to t("seeds.packet.mark_#{choice}"), packet_label_path(seed, photo, choice), method: :patch, class: 'me-2'
- not_packet_path = packet_label_path(seed, photo, '')
= link_to t('seeds.packet.not_packet'), not_packet_path, method: :patch, class: 'me-2' if label
+1 -4
View File
@@ -5,10 +5,7 @@ namespace :packets do
# usage: rake packets:needing_ocr LIMIT=5
task needing_ocr: :environment do
limit = ENV.fetch('LIMIT', nil).to_i
associations = PhotoAssociation.of_packets
.joins('INNER JOIN seeds ON seeds.id = photo_associations.photographable_id')
.where(seeds: { packet_text: [nil, ''] })
.includes(:photo).order(:photo_id, :id)
associations = PhotoAssociation.awaiting_text.includes(:photo).order(:photo_id, :id)
associations = associations.limit(limit) if limit.positive?
associations.each do |association|
+1 -1
View File
@@ -31,12 +31,12 @@ Needs only the python3 standard library, and whatever OCR command is used.
import argparse
import json
import os
import re
import shlex
import subprocess
import sys
import tempfile
import os
import urllib.error
import urllib.request
@@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'rails_helper'
describe PhotoAssociation, '.awaiting_text' do
let(:seed) { create(:seed) }
def attach(item, label)
item.photo_associations.create!(photo: create(:photo, owner: item.owner), label:)
end
it 'has the photos of a packet whose seed has no text' do
association = attach(seed, 'back')
expect(described_class.awaiting_text).to eq [association]
end
it 'has the photos of a packet whose seed has only empty text' do
seed.update!(packet_text: '')
association = attach(seed, 'front')
expect(described_class.awaiting_text).to eq [association]
end
it 'leaves out a seed that has its text' do
attach(create(:seed, packet_text: 'Sow by: December 2026'), 'back')
expect(described_class.awaiting_text).to be_empty
end
it 'leaves out a photo that is not of the packet' do
attach(seed, nil)
expect(described_class.awaiting_text).to be_empty
end
it 'leaves out the photos of something that is not a seed' do
attach(create(:job), 'before')
expect(described_class.awaiting_text).to be_empty
end
end
+13
View File
@@ -21,4 +21,17 @@ describe Seed, '#packet_photo' do
it('is nothing') { expect(seed.packet_photo('back')).to be_nil }
end
describe '#label_of' do
it('is front for the front') { expect(seed.reload.label_of(front)).to eq 'front' }
it('is back for the back') { expect(seed.reload.label_of(back)).to eq 'back' }
it('is nothing for a photo that is not of the packet') do
expect(seed.reload.label_of(seed.photos.reject { |photo| [front, back].include?(photo) }.first)).to be_nil
end
it('is nothing for a photo that is not of this seed') do
expect(seed.reload.label_of(create(:photo, owner: seed.owner))).to be_nil
end
end
end