From 6962b0e2185e58c18b732821c530e4e78c73ce48 Mon Sep 17 00:00:00 2001 From: smilerz Date: Wed, 17 Feb 2021 07:09:19 -0600 Subject: [PATCH] added new keyword management page --- cookbook/tables.py | 23 ++++ cookbook/templates/manage/keyword_table.html | 109 +++++++++++++++++++ cookbook/templates/manage/keywords.html | 22 ++++ cookbook/urls.py | 49 ++++++--- cookbook/views/__init__.py | 2 + cookbook/views/manage.py | 19 ++++ 6 files changed, 208 insertions(+), 16 deletions(-) create mode 100644 cookbook/templates/manage/keyword_table.html create mode 100644 cookbook/templates/manage/keywords.html create mode 100644 cookbook/views/manage.py diff --git a/cookbook/tables.py b/cookbook/tables.py index a5c1cca82..299cadb41 100644 --- a/cookbook/tables.py +++ b/cookbook/tables.py @@ -1,4 +1,5 @@ import django_tables2 as tables +from django.db.models.functions import Lower from django.utils.html import format_html from django.utils.translation import gettext as _ from django_tables2.utils import A @@ -61,6 +62,28 @@ class KeywordTable(tables.Table): fields = ('id', 'icon', 'name') +class ManageKeywordTable(tables.Table): + name = tables.LinkColumn('edit_keyword', args=[ + A('id')]) + + class Meta: + model = Keyword + template_name = 'manage/keyword_table.html' + fields = ('id', 'name') + + def render_name(self, value, record): + if record.icon != None: + return format_html("{} {}", record.icon, value) + else: + return value + + def order_name(self, queryset, is_descending): + queryset = queryset.annotate( + name_lower=Lower('name') + ).order_by(("-" if is_descending else "") + "name_lower") + return (queryset, True) + + class IngredientTable(tables.Table): id = tables.LinkColumn('edit_food', args=[A('id')]) diff --git a/cookbook/templates/manage/keyword_table.html b/cookbook/templates/manage/keyword_table.html new file mode 100644 index 000000000..a1e57d118 --- /dev/null +++ b/cookbook/templates/manage/keyword_table.html @@ -0,0 +1,109 @@ +{% load crispy_forms_tags %} +{% load i18n %} +{% load django_tables2 %} + + +{% block content %} + +
+ {% block table %} + + {% block table.thead %} + {% if table.show_header %} + + + {% for column in table.columns %} + + {% endfor %} + + + {% endif %} + {% endblock table.thead %} + {% block table.tbody %} + + {% for row in table.paginated_rows %} + {% block table.tbody.row %} + + {% for column, cell in row.items %} + + {% endfor %} + + {% endblock table.tbody.row %} + {% empty %} + {% if table.empty_text %} + {% block table.tbody.empty_text %} + + + + {% endblock table.tbody.empty_text %} + {% endif %} + {% endfor %} + + {% endblock table.tbody %} + {% block table.tfoot %} + {% if table.has_footer %} + + + {% for column in table.columns %} + + {% endfor %} + + + {% endif %} + {% endblock table.tfoot %} +
+ {% if column.orderable %} + {{ column.header }} + {% else %} + {{ column.header }} + {% endif %} +
+ {% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %} + {{ cell|localize }}{% else %}{{ cell|unlocalize }} + {% endif %}{% endif %}
{{ table.empty_text }}
{{ column.footer }}
+ {% endblock table %} + + {% block pagination %} + {% if table.page and table.paginator.num_pages > 1 %} + + {% endif %} + {% endblock pagination %} +
+{% endblock content %} \ No newline at end of file diff --git a/cookbook/templates/manage/keywords.html b/cookbook/templates/manage/keywords.html new file mode 100644 index 000000000..efbdabe5a --- /dev/null +++ b/cookbook/templates/manage/keywords.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% load i18n %} +{% load django_tables2 %} + +{% block title %}{{ title }}{% endblock %} + + +{% block content %} + +
+

{% trans 'Manage Keywords' %} {% trans 'List' %} + {% if create_url %} + + + {% endif %} +

+ + {% render_table table %} + +
+ +{% endblock content %} \ No newline at end of file diff --git a/cookbook/urls.py b/cookbook/urls.py index ba9a8d75e..5d2a501db 100644 --- a/cookbook/urls.py +++ b/cookbook/urls.py @@ -11,7 +11,7 @@ from cookbook.helper import dal from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Storage, Sync, SyncLog, get_model_name) -from .views import api, data, delete, edit, import_export, lists, new, views +from .views import api, data, delete, edit, import_export, lists, manage, new, views router = routers.DefaultRouter() router.register(r'user-name', api.UserNameViewSet, basename='username') @@ -48,7 +48,8 @@ urlpatterns = [ path('plan/entry/', views.meal_plan_entry, name='view_plan_entry'), path('shopping/', views.shopping_list, name='view_shopping'), path('shopping/', views.shopping_list, name='view_shopping'), - path('shopping/latest/', views.latest_shopping_list, name='view_shopping_latest'), + path('shopping/latest/', views.latest_shopping_list, + name='view_shopping_latest'), path('settings/', views.user_settings, name='view_settings'), path('history/', views.history, name='view_history'), path('test/', views.test, name='view_test'), @@ -58,22 +59,30 @@ urlpatterns = [ path('export/', import_export.export_recipe, name='view_export'), path('view/recipe/', views.recipe_view, name='view_recipe'), - path('view/recipe//', views.recipe_view, name='view_recipe'), + path('view/recipe//', + views.recipe_view, name='view_recipe'), - path('new/recipe-import//', new.create_new_external_recipe, name='new_recipe_import'), + path('new/recipe-import//', + new.create_new_external_recipe, name='new_recipe_import'), path('new/share-link//', new.share_link, name='new_share_link'), path('edit/recipe//', edit.switch_recipe, name='edit_recipe'), + path('manage/keywords/', manage.keywords, name='manage_keywords'), + # for internal use only - path('edit/recipe/internal//', edit.internal_recipe_update, name='edit_internal_recipe'), - path('edit/recipe/external//', edit.ExternalRecipeUpdate.as_view(), name='edit_external_recipe'), - path('edit/recipe/convert//', edit.convert_recipe, name='edit_convert_recipe'), + path('edit/recipe/internal//', + edit.internal_recipe_update, name='edit_internal_recipe'), + path('edit/recipe/external//', + edit.ExternalRecipeUpdate.as_view(), name='edit_external_recipe'), + path('edit/recipe/convert//', + edit.convert_recipe, name='edit_convert_recipe'), path('edit/storage//', edit.edit_storage, name='edit_storage'), path('edit/ingredient/', edit.edit_ingredients, name='edit_food'), - path('delete/recipe-source//', delete.delete_recipe_source, name='delete_recipe_source'), + path('delete/recipe-source//', + delete.delete_recipe_source, name='delete_recipe_source'), # TODO move to generic "new" view path('data/sync', data.sync, name='data_sync'), @@ -83,14 +92,19 @@ urlpatterns = [ path('data/statistics', data.statistics, name='data_stats'), path('data/import/url', data.import_url, name='data_import_url'), - path('api/get_external_file_link//', api.get_external_file_link, name='api_get_external_file_link'), - path('api/get_recipe_file//', api.get_recipe_file, name='api_get_recipe_file'), + path('api/get_external_file_link//', + api.get_external_file_link, name='api_get_external_file_link'), + path('api/get_recipe_file//', + api.get_recipe_file, name='api_get_recipe_file'), path('api/sync_all/', api.sync_all, name='api_sync'), - path('api/log_cooking//', api.log_cooking, name='api_log_cooking'), - path('api/plan-ical///', api.get_plan_ical, name='api_get_plan_ical'), + path('api/log_cooking//', + api.log_cooking, name='api_log_cooking'), + path('api/plan-ical///', + api.get_plan_ical, name='api_get_plan_ical'), path('api/recipe-from-url/', api.recipe_from_url, name='api_recipe_from_url'), path('api/backup/', api.get_backup, name='api_backup'), - path('api/ingredient-from-string/', api.ingredient_from_string, name='api_ingredient_from_string'), + path('api/ingredient-from-string/', api.ingredient_from_string, + name='api_ingredient_from_string'), path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'), path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'), @@ -99,7 +113,8 @@ urlpatterns = [ path('docs/markdown/', views.markdown_info, name='docs_markdown'), path('docs/api/', views.api_info, name='docs_api'), - path('openapi', get_schema_view(title="Django Recipes", version=VERSION_NUMBER), name='openapi-schema'), + path('openapi', get_schema_view(title="Django Recipes", + version=VERSION_NUMBER), name='openapi-schema'), path('api/', include((router.urls, 'api'))), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), @@ -107,8 +122,10 @@ urlpatterns = [ path('offline/', views.offline, name='view_offline'), - path('service-worker.js', (TemplateView.as_view(template_name="sw.js", content_type='application/javascript', )), name='service_worker'), - path('manifest.json', (TemplateView.as_view(template_name="manifest.json", content_type='application/json', )), name='web_manifest'), + path('service-worker.js', (TemplateView.as_view(template_name="sw.js", + content_type='application/javascript', )), name='service_worker'), + path('manifest.json', (TemplateView.as_view(template_name="manifest.json", + content_type='application/json', )), name='web_manifest'), ] generic_models = ( diff --git a/cookbook/views/__init__.py b/cookbook/views/__init__.py index ab37dad90..52127cb1a 100644 --- a/cookbook/views/__init__.py +++ b/cookbook/views/__init__.py @@ -6,6 +6,7 @@ import cookbook.views.import_export import cookbook.views.lists import cookbook.views.new import cookbook.views.views +import cookbook.views.manage __all__ = [ 'api', @@ -14,6 +15,7 @@ __all__ = [ 'edit', 'import_export', 'lists', + 'manage', 'new', 'views', ] diff --git a/cookbook/views/manage.py b/cookbook/views/manage.py new file mode 100644 index 000000000..db126cc4f --- /dev/null +++ b/cookbook/views/manage.py @@ -0,0 +1,19 @@ +from cookbook.helper.permission_helper import group_required +from cookbook.models import Keyword +from cookbook.tables import ManageKeywordTable +from django.views import generic +from django.shortcuts import render +from django_tables2 import RequestConfig +from django.utils.translation import gettext as _ + + +@group_required('user') +def keywords(request): + table = ManageKeywordTable(Keyword.objects.all()) + RequestConfig(request, paginate={'per_page': 25}).configure(table) + + return render( + request, + 'manage/keywords.html', + {'title': _("Keyword"), 'table': table, 'create_url': 'new_keyword'} + )