Coverage for apps / legacy / views.py: 66%

144 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 00:40 +0000

1"""Views for legacy frontend.""" 

2 

3from django.shortcuts import render, redirect, get_object_or_404 

4 

5from apps.core.models import AppSettings 

6from apps.profiles.models import Profile 

7from apps.ai.models import AIPrompt 

8from apps.ai.services.openrouter import OpenRouterService, AIUnavailableError, AIResponseError 

9from apps.recipes.models import ( 

10 Recipe, 

11 RecipeCollection, 

12 RecipeFavorite, 

13 RecipeViewHistory, 

14) 

15 

16 

17def _is_ai_available() -> bool: 

18 """Check if AI features are available (key configured AND valid).""" 

19 settings = AppSettings.get() 

20 if not settings.openrouter_api_key: 

21 return False 

22 is_valid, _ = OpenRouterService.validate_key_cached() 

23 return is_valid 

24 

25 

26def profile_selector(request): 

27 """Profile selector screen.""" 

28 profiles = list(Profile.objects.all().values( 

29 'id', 'name', 'avatar_color', 'theme', 'unit_preference' 

30 )) 

31 return render(request, 'legacy/profile_selector.html', { 

32 'profiles': profiles, 

33 }) 

34 

35 

36def home(request): 

37 """Home screen.""" 

38 profile_id = request.session.get('profile_id') 

39 if not profile_id: 

40 return redirect('legacy:profile_selector') 

41 

42 try: 

43 profile = Profile.objects.get(id=profile_id) 

44 except Profile.DoesNotExist: 

45 del request.session['profile_id'] 

46 return redirect('legacy:profile_selector') 

47 

48 # Get favorites for this profile 

49 favorites_qs = RecipeFavorite.objects.filter( 

50 profile=profile 

51 ).select_related('recipe').order_by('-created_at') 

52 favorites_count = favorites_qs.count() 

53 favorites = favorites_qs[:12] 

54 

55 # Get recently viewed for this profile 

56 history_qs = RecipeViewHistory.objects.filter( 

57 profile=profile 

58 ).select_related('recipe').order_by('-viewed_at') 

59 history_count = history_qs.count() 

60 history = history_qs[:6] 

61 

62 # Build favorite recipe IDs set for checking 

63 favorite_recipe_ids = set(f.recipe_id for f in favorites) 

64 

65 # Check if AI features are available 

66 ai_available = _is_ai_available() 

67 

68 return render(request, 'legacy/home.html', { 

69 'profile': { 

70 'id': profile.id, 

71 'name': profile.name, 

72 'avatar_color': profile.avatar_color, 

73 }, 

74 'favorites': favorites, 

75 'favorites_count': favorites_count, 

76 'history': history, 

77 'history_count': history_count, 

78 'favorite_recipe_ids': favorite_recipe_ids, 

79 'ai_available': ai_available, 

80 }) 

81 

82 

83def search(request): 

84 """Search results screen.""" 

85 profile_id = request.session.get('profile_id') 

86 if not profile_id: 

87 return redirect('legacy:profile_selector') 

88 

89 try: 

90 profile = Profile.objects.get(id=profile_id) 

91 except Profile.DoesNotExist: 

92 del request.session['profile_id'] 

93 return redirect('legacy:profile_selector') 

94 

95 query = request.GET.get('q', '') 

96 # Detect if query is a URL 

97 is_url = query.strip().startswith('http://') or query.strip().startswith('https://') 

98 

99 return render(request, 'legacy/search.html', { 

100 'profile': { 

101 'id': profile.id, 

102 'name': profile.name, 

103 'avatar_color': profile.avatar_color, 

104 }, 

105 'query': query, 

106 'is_url': is_url, 

107 }) 

108 

109 

110def recipe_detail(request, recipe_id): 

111 """Recipe detail screen.""" 

112 profile_id = request.session.get('profile_id') 

113 if not profile_id: 

114 return redirect('legacy:profile_selector') 

115 

116 try: 

117 profile = Profile.objects.get(id=profile_id) 

118 except Profile.DoesNotExist: 

119 del request.session['profile_id'] 

120 return redirect('legacy:profile_selector') 

121 

122 # Get the recipe (must belong to this profile) 

123 recipe = get_object_or_404(Recipe, id=recipe_id, profile=profile) 

124 

125 # Record view history 

126 RecipeViewHistory.objects.update_or_create( 

127 profile=profile, 

128 recipe=recipe, 

129 defaults={}, # Just update viewed_at (auto_now) 

130 ) 

131 

132 # Check if recipe is favorited 

133 is_favorite = RecipeFavorite.objects.filter( 

134 profile=profile, 

135 recipe=recipe, 

136 ).exists() 

137 

138 # Get user's collections for the "add to collection" feature 

139 collections = RecipeCollection.objects.filter(profile=profile) 

140 

141 # Check if AI features are available 

142 ai_available = _is_ai_available() 

143 

144 # Prepare ingredient groups or flat list 

145 has_ingredient_groups = bool(recipe.ingredient_groups) 

146 

147 # Prepare instructions 

148 instructions = recipe.instructions 

149 if not instructions and recipe.instructions_text: 

150 instructions = [s.strip() for s in recipe.instructions_text.split('\n') if s.strip()] 

151 

152 return render(request, 'legacy/recipe_detail.html', { 

153 'profile': { 

154 'id': profile.id, 

155 'name': profile.name, 

156 'avatar_color': profile.avatar_color, 

157 }, 

158 'recipe': recipe, 

159 'is_favorite': is_favorite, 

160 'collections': collections, 

161 'ai_available': ai_available, 

162 'has_ingredient_groups': has_ingredient_groups, 

163 'instructions': instructions, 

164 }) 

165 

166 

167def play_mode(request, recipe_id): 

168 """Play mode / cooking mode screen.""" 

169 profile_id = request.session.get('profile_id') 

170 if not profile_id: 

171 return redirect('legacy:profile_selector') 

172 

173 try: 

174 profile = Profile.objects.get(id=profile_id) 

175 except Profile.DoesNotExist: 

176 del request.session['profile_id'] 

177 return redirect('legacy:profile_selector') 

178 

179 # Get the recipe (must belong to this profile) 

180 recipe = get_object_or_404(Recipe, id=recipe_id, profile=profile) 

181 

182 # Check if AI features are available 

183 ai_available = _is_ai_available() 

184 

185 # Prepare instructions 

186 instructions = recipe.instructions 

187 if not instructions and recipe.instructions_text: 

188 instructions = [s.strip() for s in recipe.instructions_text.split('\n') if s.strip()] 

189 

190 return render(request, 'legacy/play_mode.html', { 

191 'profile': { 

192 'id': profile.id, 

193 'name': profile.name, 

194 'avatar_color': profile.avatar_color, 

195 }, 

196 'recipe': recipe, 

197 'instructions': instructions, 

198 'instructions_json': instructions, # For JavaScript 

199 'ai_available': ai_available, 

200 }) 

201 

202 

203def all_recipes(request): 

204 """All Recipes screen - shows all viewed recipes (history).""" 

205 profile_id = request.session.get('profile_id') 

206 if not profile_id: 

207 return redirect('legacy:profile_selector') 

208 

209 try: 

210 profile = Profile.objects.get(id=profile_id) 

211 except Profile.DoesNotExist: 

212 del request.session['profile_id'] 

213 return redirect('legacy:profile_selector') 

214 

215 # Get all history for this profile (no limit) 

216 history = RecipeViewHistory.objects.filter( 

217 profile=profile 

218 ).select_related('recipe').order_by('-viewed_at') 

219 

220 # Build set of favorite recipe IDs for display 

221 favorite_recipe_ids = set( 

222 RecipeFavorite.objects.filter(profile=profile).values_list('recipe_id', flat=True) 

223 ) 

224 

225 return render(request, 'legacy/all_recipes.html', { 

226 'profile': { 

227 'id': profile.id, 

228 'name': profile.name, 

229 'avatar_color': profile.avatar_color, 

230 }, 

231 'history': history, 

232 'favorite_recipe_ids': favorite_recipe_ids, 

233 }) 

234 

235 

236def favorites(request): 

237 """Favorites screen - shows all favorited recipes.""" 

238 profile_id = request.session.get('profile_id') 

239 if not profile_id: 

240 return redirect('legacy:profile_selector') 

241 

242 try: 

243 profile = Profile.objects.get(id=profile_id) 

244 except Profile.DoesNotExist: 

245 del request.session['profile_id'] 

246 return redirect('legacy:profile_selector') 

247 

248 # Get all favorites for this profile 

249 favorites = RecipeFavorite.objects.filter( 

250 profile=profile 

251 ).select_related('recipe').order_by('-created_at') 

252 

253 return render(request, 'legacy/favorites.html', { 

254 'profile': { 

255 'id': profile.id, 

256 'name': profile.name, 

257 'avatar_color': profile.avatar_color, 

258 }, 

259 'favorites': favorites, 

260 }) 

261 

262 

263def collections(request): 

264 """Collections list screen.""" 

265 profile_id = request.session.get('profile_id') 

266 if not profile_id: 

267 return redirect('legacy:profile_selector') 

268 

269 try: 

270 profile = Profile.objects.get(id=profile_id) 

271 except Profile.DoesNotExist: 

272 del request.session['profile_id'] 

273 return redirect('legacy:profile_selector') 

274 

275 # Get all collections for this profile 

276 collections = RecipeCollection.objects.filter( 

277 profile=profile 

278 ).prefetch_related('items__recipe').order_by('-updated_at') 

279 

280 return render(request, 'legacy/collections.html', { 

281 'profile': { 

282 'id': profile.id, 

283 'name': profile.name, 

284 'avatar_color': profile.avatar_color, 

285 }, 

286 'collections': collections, 

287 }) 

288 

289 

290def collection_detail(request, collection_id): 

291 """Collection detail screen - shows recipes in a collection.""" 

292 profile_id = request.session.get('profile_id') 

293 if not profile_id: 

294 return redirect('legacy:profile_selector') 

295 

296 try: 

297 profile = Profile.objects.get(id=profile_id) 

298 except Profile.DoesNotExist: 

299 del request.session['profile_id'] 

300 return redirect('legacy:profile_selector') 

301 

302 # Get the collection (must belong to this profile) 

303 collection = get_object_or_404( 

304 RecipeCollection, id=collection_id, profile=profile 

305 ) 

306 

307 # Get all items in this collection 

308 items = collection.items.select_related('recipe').order_by('order', '-added_at') 

309 

310 # Build set of favorite recipe IDs for display 

311 favorite_recipe_ids = set( 

312 RecipeFavorite.objects.filter(profile=profile).values_list('recipe_id', flat=True) 

313 ) 

314 

315 return render(request, 'legacy/collection_detail.html', { 

316 'profile': { 

317 'id': profile.id, 

318 'name': profile.name, 

319 'avatar_color': profile.avatar_color, 

320 }, 

321 'collection': collection, 

322 'items': items, 

323 'favorite_recipe_ids': favorite_recipe_ids, 

324 }) 

325 

326 

327def settings(request): 

328 """Settings screen - AI prompts and sources configuration.""" 

329 profile_id = request.session.get('profile_id') 

330 if not profile_id: 

331 return redirect('legacy:profile_selector') 

332 

333 try: 

334 profile = Profile.objects.get(id=profile_id) 

335 except Profile.DoesNotExist: 

336 del request.session['profile_id'] 

337 return redirect('legacy:profile_selector') 

338 

339 # Get app settings 

340 app_settings = AppSettings.get() 

341 

342 # Check if AI features are available 

343 ai_available = _is_ai_available() 

344 

345 # Get all AI prompts 

346 prompts = list(AIPrompt.objects.all().order_by('name')) 

347 

348 # Get available models from OpenRouter 

349 try: 

350 service = OpenRouterService() 

351 models = service.get_available_models() 

352 except (AIUnavailableError, AIResponseError): 

353 models = [] 

354 

355 return render(request, 'legacy/settings.html', { 

356 'profile': { 

357 'id': profile.id, 

358 'name': profile.name, 

359 'avatar_color': profile.avatar_color, 

360 }, 

361 'current_profile_id': profile_id, 

362 'ai_available': ai_available, 

363 'default_model': app_settings.default_ai_model, 

364 'prompts': prompts, 

365 'models': models, 

366 }) 

← Back to Dashboard