Description
`JurorDAO.fave()` in `montage/rdb.py` does not scope its existing-fave lookup to the current campaign. If the same image appears in two campaigns, faving it in the second campaign reactivates the first campaign's record instead of creating a new one — attributing the fave to the wrong campaign.
Setup
Two campaigns, both containing the same image (`File:Photo.jpg`):
| Campaign |
Round |
Image |
| Campaign A |
Round 1 |
`File:Photo.jpg` |
| — |
— |
— |
| Campaign B |
Round 1 |
`File:Photo.jpg` |
Reproduction
Verified against local dev instance. Requires two rounds that share at least one entry, with the same juror in both. In the default dev database, entry 6 (`Test_WLM_2015_image_001.jpg`) appears in round 4 (campaign 2) and round 6 (campaign 4), with Effeietsanders as a juror in both.
Generate a dev cookie first:
from clastic.middleware.cookie import JSONCookie
cookie = JSONCookie(
{'userid': 409, 'username': 'Effeietsanders'},
secret_key='Replace221432ThisWithSomethingSomewhatSecret'
)
print(cookie.serialize())
Then run:
import requests, sqlite3
BASE = 'http://localhost:5001/v1'
COOKIE = '<output from above>'
s = requests.Session()
s.cookies.set('clastic_cookie', COOKIE)
s.headers.update({'Content-Type': 'application/json'})
ROUND_A_ID = 4 # campaign_id = 2
ROUND_B_ID = 6 # campaign_id = 4
ENTRY_ID = 6 # Test_WLM_2015_image_001.jpg, present in both rounds
def db_faves():
conn = sqlite3.connect('tmp_montage.db')
cur = conn.cursor()
cur.execute('SELECT id, entry_id, campaign_id, status FROM favorites WHERE entry_id = ?', (ENTRY_ID,))
rows = cur.fetchall()
conn.close()
return rows
# Clean slate
conn = sqlite3.connect('tmp_montage.db')
conn.execute('DELETE FROM favorites WHERE entry_id = ?', (ENTRY_ID,))
conn.commit()
# Step 1: fave in Campaign A
s.post(f'{BASE}/juror/round/{ROUND_A_ID}/{ENTRY_ID}/fave', data='{}')
print("After fave in Campaign A:", db_faves())
# [(1, 6, 2, 'active')]
# Step 2: unfave in Campaign A
s.post(f'{BASE}/juror/round/{ROUND_A_ID}/{ENTRY_ID}/unfave', data='{}')
print("After unfave in Campaign A:", db_faves())
# [(1, 6, 2, 'cancelled')]
# Step 3: fave the same image in Campaign B
s.post(f'{BASE}/juror/round/{ROUND_B_ID}/{ENTRY_ID}/fave', data='{}')
rows = db_faves()
print("After fave in Campaign B:", rows)
# BUG: [(1, 6, 2, 'active')] — campaign_id=2 (Campaign A), not campaign_id=4 (Campaign B)
# EXPECTED: a new record with campaign_id=4
Actual output (bug):
After fave in Campaign A: [(1, 6, 2, 'active')]
After unfave in Campaign A: [(1, 6, 2, 'cancelled')]
After fave in Campaign B: [(1, 6, 2, 'active')] ← campaign_id=2, wrong campaign
Cause
In `rdb.py`, `JurorDAO.fave()`:
existing_fave = (self.query(Favorite)
.filter_by(entry_id=entry_id,
user=self.user)
.first()) # missing campaign_id filter
`campaign_id` is not included in the lookup, so faves are matched across all campaigns.
Fix
Resolve `round_entry` before the existing-fave check and add `campaign_id` to the filter:
round_entry = self.get_round_entry(round_id, entry_id)
campaign_id = round_entry.round.campaign.id
existing_fave = (self.query(Favorite)
.filter_by(entry_id=entry_id,
campaign_id=campaign_id,
user=self.user)
.first())
Description
`JurorDAO.fave()` in `montage/rdb.py` does not scope its existing-fave lookup to the current campaign. If the same image appears in two campaigns, faving it in the second campaign reactivates the first campaign's record instead of creating a new one — attributing the fave to the wrong campaign.
Setup
Two campaigns, both containing the same image (`File:Photo.jpg`):
Reproduction
Verified against local dev instance. Requires two rounds that share at least one entry, with the same juror in both. In the default dev database, entry 6 (`Test_WLM_2015_image_001.jpg`) appears in round 4 (campaign 2) and round 6 (campaign 4), with Effeietsanders as a juror in both.
Generate a dev cookie first:
Then run:
Actual output (bug):
Cause
In `rdb.py`, `JurorDAO.fave()`:
`campaign_id` is not included in the lookup, so faves are matched across all campaigns.
Fix
Resolve `round_entry` before the existing-fave check and add `campaign_id` to the filter: