Coverage for cookbook/admin.py: 85%
233 statements
« prev ^ index » next coverage.py v7.4.0, created at 2023-12-29 00:47 +0100
« prev ^ index » next coverage.py v7.4.0, created at 2023-12-29 00:47 +0100
1from django.conf import settings
2from django.contrib import admin
3from django.contrib.auth.admin import UserAdmin
4from django.contrib.auth.models import Group, User
5from django.contrib.postgres.search import SearchVector
6from django.utils import translation
7from django_scopes import scopes_disabled
8from treebeard.admin import TreeAdmin
9from treebeard.forms import movenodeform_factory
11from cookbook.managers import DICTIONARY
13from .models import (BookmarkletImport, Comment, CookLog, Food, ImportLog, Ingredient, InviteLink,
14 Keyword, MealPlan, MealType, NutritionInformation, Property, PropertyType,
15 Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink,
16 ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
17 Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog,
18 TelegramBot, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
19 ViewLog)
22class CustomUserAdmin(UserAdmin):
23 def has_add_permission(self, request, obj=None):
24 return False
27admin.site.unregister(User)
28admin.site.register(User, CustomUserAdmin)
30admin.site.unregister(Group)
33@admin.action(description='Delete all data from a space')
34def delete_space_action(modeladmin, request, queryset):
35 for space in queryset:
36 space.safe_delete()
39class SpaceAdmin(admin.ModelAdmin):
40 list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
41 search_fields = ('name', 'created_by__username')
42 autocomplete_fields = ('created_by',)
43 filter_horizontal = ('food_inherit',)
44 list_filter = ('max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
45 date_hierarchy = 'created_at'
46 actions = [delete_space_action]
49admin.site.register(Space, SpaceAdmin)
52class UserSpaceAdmin(admin.ModelAdmin):
53 list_display = ('user', 'space',)
54 search_fields = ('user__username', 'space__name',)
55 filter_horizontal = ('groups',)
56 autocomplete_fields = ('user', 'space',)
59admin.site.register(UserSpace, UserSpaceAdmin)
62class UserPreferenceAdmin(admin.ModelAdmin):
63 list_display = ('name', 'theme', 'nav_color', 'default_page')
64 search_fields = ('user__username',)
65 list_filter = ('theme', 'nav_color', 'default_page',)
66 date_hierarchy = 'created_at'
67 filter_horizontal = ('plan_share', 'shopping_share',)
69 @staticmethod
70 def name(obj):
71 return obj.user.get_user_display_name()
74admin.site.register(UserPreference, UserPreferenceAdmin)
77class SearchPreferenceAdmin(admin.ModelAdmin):
78 list_display = ('name', 'search', 'trigram_threshold', )
79 search_fields = ('user__username',)
80 list_filter = ('search',)
82 @staticmethod
83 def name(obj):
84 return obj.user.get_user_display_name()
87admin.site.register(SearchPreference, SearchPreferenceAdmin)
90class StorageAdmin(admin.ModelAdmin):
91 list_display = ('name', 'method')
92 search_fields = ('name',)
95admin.site.register(Storage, StorageAdmin)
98class SyncAdmin(admin.ModelAdmin):
99 list_display = ('storage', 'path', 'active', 'last_checked')
100 search_fields = ('storage__name', 'path')
103admin.site.register(Sync, SyncAdmin)
106class SupermarketCategoryInline(admin.TabularInline):
107 model = SupermarketCategoryRelation
110class SupermarketAdmin(admin.ModelAdmin):
111 list_display = ('name', 'space',)
112 inlines = (SupermarketCategoryInline,)
115class SupermarketCategoryAdmin(admin.ModelAdmin):
116 list_display = ('name', 'space',)
119admin.site.register(Supermarket, SupermarketAdmin)
120admin.site.register(SupermarketCategory, SupermarketCategoryAdmin)
123class SyncLogAdmin(admin.ModelAdmin):
124 list_display = ('sync', 'status', 'msg', 'created_at')
127admin.site.register(SyncLog, SyncLogAdmin)
130@admin.action(description='Temporarily ENABLE sorting on Foods and Keywords.')
131def enable_tree_sorting(modeladmin, request, queryset):
132 Food.node_order_by = ['name']
133 Keyword.node_order_by = ['name']
134 with scopes_disabled():
135 Food.fix_tree(fix_paths=True)
136 Keyword.fix_tree(fix_paths=True)
139@admin.action(description='Temporarily DISABLE sorting on Foods and Keywords.')
140def disable_tree_sorting(modeladmin, request, queryset):
141 Food.node_order_by = []
142 Keyword.node_order_by = []
145@admin.action(description='Fix problems and sort tree by name')
146def sort_tree(modeladmin, request, queryset):
147 orginal_value = modeladmin.model.node_order_by[:]
148 modeladmin.model.node_order_by = ['name']
149 with scopes_disabled():
150 modeladmin.model.fix_tree(fix_paths=True)
151 modeladmin.model.node_order_by = orginal_value
154class KeywordAdmin(TreeAdmin):
155 form = movenodeform_factory(Keyword)
156 ordering = ('space', 'path',)
157 search_fields = ('name',)
158 actions = [sort_tree, enable_tree_sorting, disable_tree_sorting]
161admin.site.register(Keyword, KeywordAdmin)
164@admin.action(description='Delete Steps not part of a Recipe.')
165def delete_unattached_steps(modeladmin, request, queryset):
166 with scopes_disabled():
167 Step.objects.filter(recipe=None).delete()
170class StepAdmin(admin.ModelAdmin):
171 list_display = ('recipe_and_name', 'order', 'space')
172 ordering = ('recipe__name', 'name', 'space', )
173 search_fields = ('name', 'recipe__name')
174 actions = [delete_unattached_steps]
176 @staticmethod
177 @admin.display(description="Name")
178 def recipe_and_name(obj):
179 if not obj.recipe_set.exists():
180 return f"Orphaned Step{'':s if not obj.name else f': {obj.name}'}"
181 return f"{obj.recipe_set.first().name}: {obj.name}" if obj.name else obj.recipe_set.first().name
184admin.site.register(Step, StepAdmin)
187@admin.action(description='Rebuild index for selected recipes')
188def rebuild_index(modeladmin, request, queryset):
189 language = DICTIONARY.get(translation.get_language(), 'simple')
190 with scopes_disabled():
191 Recipe.objects.all().update(
192 name_search_vector=SearchVector('name__unaccent', weight='A', config=language),
193 desc_search_vector=SearchVector('description__unaccent', weight='B', config=language)
194 )
195 Step.objects.all().update(search_vector=SearchVector('instruction__unaccent', weight='B', config=language))
198class RecipeAdmin(admin.ModelAdmin):
199 list_display = ('name', 'internal', 'created_by', 'storage', 'space')
200 search_fields = ('name', 'created_by__username')
201 ordering = ('name', 'created_by__username', )
202 list_filter = ('internal',)
203 date_hierarchy = 'created_at'
205 @staticmethod
206 def created_by(obj):
207 return obj.created_by.get_user_display_name()
209 if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql':
210 actions = [rebuild_index]
213admin.site.register(Recipe, RecipeAdmin)
216class UnitAdmin(admin.ModelAdmin):
217 list_display = ('name', 'space')
218 ordering = ('name', 'space', )
219 search_fields = ('name',)
222admin.site.register(Unit, UnitAdmin)
225# admin.site.register(FoodInheritField)
228class FoodAdmin(TreeAdmin):
229 form = movenodeform_factory(Keyword)
230 ordering = ('space', 'path',)
231 search_fields = ('name',)
232 actions = [sort_tree, enable_tree_sorting, disable_tree_sorting]
235admin.site.register(Food, FoodAdmin)
238class UnitConversionAdmin(admin.ModelAdmin):
239 list_display = ('base_amount', 'base_unit', 'food', 'converted_amount', 'converted_unit')
240 search_fields = ('food__name', 'unit__name')
243admin.site.register(UnitConversion, UnitConversionAdmin)
246@admin.action(description='Delete Ingredients not part of a Recipe.')
247def delete_unattached_ingredients(modeladmin, request, queryset):
248 with scopes_disabled():
249 Ingredient.objects.filter(step__recipe=None).delete()
252class IngredientAdmin(admin.ModelAdmin):
253 list_display = ('recipe_name', 'amount', 'unit', 'food', 'space')
254 search_fields = ('food__name', 'unit__name', 'step__recipe__name')
255 actions = [delete_unattached_ingredients]
257 @staticmethod
258 @admin.display(description="Recipe")
259 def recipe_name(obj):
260 recipes = obj.step_set.first().recipe_set.all() if obj.step_set.exists() else None
261 return recipes.first().name if recipes else 'Orphaned Ingredient'
264admin.site.register(Ingredient, IngredientAdmin)
267class CommentAdmin(admin.ModelAdmin):
268 list_display = ('recipe', 'name', 'created_at')
269 search_fields = ('text', 'created_by__username')
270 date_hierarchy = 'created_at'
272 @staticmethod
273 def name(obj):
274 return obj.created_by.get_user_display_name()
277admin.site.register(Comment, CommentAdmin)
280class RecipeImportAdmin(admin.ModelAdmin):
281 list_display = ('name', 'storage', 'file_path')
284admin.site.register(RecipeImport, RecipeImportAdmin)
287class RecipeBookAdmin(admin.ModelAdmin):
288 list_display = ('name', 'user_name', 'space')
289 search_fields = ('name', 'created_by__username')
291 @staticmethod
292 def user_name(obj):
293 return obj.created_by.get_user_display_name()
296admin.site.register(RecipeBook, RecipeBookAdmin)
299class RecipeBookEntryAdmin(admin.ModelAdmin):
300 list_display = ('book', 'recipe')
303admin.site.register(RecipeBookEntry, RecipeBookEntryAdmin)
306class MealPlanAdmin(admin.ModelAdmin):
307 list_display = ('user', 'recipe', 'meal_type', 'from_date', 'to_date')
309 @staticmethod
310 def user(obj):
311 return obj.created_by.get_user_display_name()
314admin.site.register(MealPlan, MealPlanAdmin)
317class MealTypeAdmin(admin.ModelAdmin):
318 list_display = ('name', 'created_by', 'order')
319 search_fields = ('name', 'created_by__username')
322admin.site.register(MealType, MealTypeAdmin)
325class ViewLogAdmin(admin.ModelAdmin):
326 list_display = ('recipe', 'created_by', 'created_at')
329admin.site.register(ViewLog, ViewLogAdmin)
332class InviteLinkAdmin(admin.ModelAdmin):
333 list_display = (
334 'group', 'valid_until', 'space',
335 'created_by', 'created_at', 'used_by'
336 )
339admin.site.register(InviteLink, InviteLinkAdmin)
342class CookLogAdmin(admin.ModelAdmin):
343 list_display = ('recipe', 'created_by', 'created_at', 'rating', 'servings')
344 search_fields = ('recipe__name', 'space__name',)
347admin.site.register(CookLog, CookLogAdmin)
350class ShoppingListRecipeAdmin(admin.ModelAdmin):
351 list_display = ('id', 'recipe', 'servings')
354admin.site.register(ShoppingListRecipe, ShoppingListRecipeAdmin)
357class ShoppingListEntryAdmin(admin.ModelAdmin):
358 list_display = ('id', 'food', 'unit', 'list_recipe', 'created_by', 'created_at', 'checked')
361admin.site.register(ShoppingListEntry, ShoppingListEntryAdmin)
364# class ShoppingListAdmin(admin.ModelAdmin):
365# list_display = ('id', 'created_by', 'created_at')
368# admin.site.register(ShoppingList, ShoppingListAdmin)
371class ShareLinkAdmin(admin.ModelAdmin):
372 list_display = ('recipe', 'created_by', 'uuid', 'created_at',)
375admin.site.register(ShareLink, ShareLinkAdmin)
378class PropertyTypeAdmin(admin.ModelAdmin):
379 search_fields = ('space',)
381 list_display = ('id', 'space', 'name', 'fdc_id')
384admin.site.register(PropertyType, PropertyTypeAdmin)
387class PropertyAdmin(admin.ModelAdmin):
388 list_display = ('property_amount', 'property_type')
391admin.site.register(Property, PropertyAdmin)
394class NutritionInformationAdmin(admin.ModelAdmin):
395 list_display = ('id',)
398admin.site.register(NutritionInformation, NutritionInformationAdmin)
401class ImportLogAdmin(admin.ModelAdmin):
402 list_display = ('id', 'type', 'running', 'created_by', 'created_at',)
405admin.site.register(ImportLog, ImportLogAdmin)
408class TelegramBotAdmin(admin.ModelAdmin):
409 list_display = ('id', 'name', 'created_by',)
412admin.site.register(TelegramBot, TelegramBotAdmin)
415class BookmarkletImportAdmin(admin.ModelAdmin):
416 list_display = ('id', 'url', 'created_by', 'created_at',)
419admin.site.register(BookmarkletImport, BookmarkletImportAdmin)
422class UserFileAdmin(admin.ModelAdmin):
423 list_display = ('id', 'name', 'file_size_kb', 'created_at',)
426admin.site.register(UserFile, UserFileAdmin)