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
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 00:40 +0000
1"""Views for legacy frontend."""
3from django.shortcuts import render, redirect, get_object_or_404
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)
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
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 })
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')
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')
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]
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]
62 # Build favorite recipe IDs set for checking
63 favorite_recipe_ids = set(f.recipe_id for f in favorites)
65 # Check if AI features are available
66 ai_available = _is_ai_available()
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 })
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')
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')
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://')
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 })
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')
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')
122 # Get the recipe (must belong to this profile)
123 recipe = get_object_or_404(Recipe, id=recipe_id, profile=profile)
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 )
132 # Check if recipe is favorited
133 is_favorite = RecipeFavorite.objects.filter(
134 profile=profile,
135 recipe=recipe,
136 ).exists()
138 # Get user's collections for the "add to collection" feature
139 collections = RecipeCollection.objects.filter(profile=profile)
141 # Check if AI features are available
142 ai_available = _is_ai_available()
144 # Prepare ingredient groups or flat list
145 has_ingredient_groups = bool(recipe.ingredient_groups)
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()]
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 })
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')
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')
179 # Get the recipe (must belong to this profile)
180 recipe = get_object_or_404(Recipe, id=recipe_id, profile=profile)
182 # Check if AI features are available
183 ai_available = _is_ai_available()
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()]
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 })
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')
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')
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')
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 )
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 })
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')
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')
248 # Get all favorites for this profile
249 favorites = RecipeFavorite.objects.filter(
250 profile=profile
251 ).select_related('recipe').order_by('-created_at')
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 })
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')
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')
275 # Get all collections for this profile
276 collections = RecipeCollection.objects.filter(
277 profile=profile
278 ).prefetch_related('items__recipe').order_by('-updated_at')
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 })
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')
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')
302 # Get the collection (must belong to this profile)
303 collection = get_object_or_404(
304 RecipeCollection, id=collection_id, profile=profile
305 )
307 # Get all items in this collection
308 items = collection.items.select_related('recipe').order_by('order', '-added_at')
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 )
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 })
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')
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')
339 # Get app settings
340 app_settings = AppSettings.get()
342 # Check if AI features are available
343 ai_available = _is_ai_available()
345 # Get all AI prompts
346 prompts = list(AIPrompt.objects.all().order_by('name'))
348 # Get available models from OpenRouter
349 try:
350 service = OpenRouterService()
351 models = service.get_available_models()
352 except (AIUnavailableError, AIResponseError):
353 models = []
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 })