added new keyword management page

This commit is contained in:
smilerz
2021-02-17 07:09:19 -06:00
parent 505650518e
commit 6962b0e218
6 changed files with 208 additions and 16 deletions

View File

@ -1,4 +1,5 @@
import django_tables2 as tables import django_tables2 as tables
from django.db.models.functions import Lower
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_tables2.utils import A from django_tables2.utils import A
@ -61,6 +62,28 @@ class KeywordTable(tables.Table):
fields = ('id', 'icon', 'name') 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): class IngredientTable(tables.Table):
id = tables.LinkColumn('edit_food', args=[A('id')]) id = tables.LinkColumn('edit_food', args=[A('id')])

View File

@ -0,0 +1,109 @@
{% load crispy_forms_tags %}
{% load i18n %}
{% load django_tables2 %}
{% block content %}
<div class="table-container">
{% block table %}
<table {% render_attrs table.attrs class="table" %}>
{% block table.thead %}
{% if table.show_header %}
<thead {{ table.attrs.thead.as_html }}>
<tr>
{% for column in table.columns %}
<th {{ column.attrs.th.as_html }}>
{% if column.orderable %}
<a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header }}</a>
{% else %}
{{ column.header }}
{% endif %}
</th>
{% endfor %}
</tr>
</thead>
{% endif %}
{% endblock table.thead %}
{% block table.tbody %}
<tbody {{ table.attrs.tbody.as_html }}>
{% for row in table.paginated_rows %}
{% block table.tbody.row %}
<tr {{ row.attrs.as_html }}>
{% for column, cell in row.items %}
<td {{ column.attrs.td.as_html }}>
{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}
{{ cell|localize }}{% else %}{{ cell|unlocalize }}
{% endif %}{% endif %}</td>
{% endfor %}
</tr>
{% endblock table.tbody.row %}
{% empty %}
{% if table.empty_text %}
{% block table.tbody.empty_text %}
<tr>
<td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td>
</tr>
{% endblock table.tbody.empty_text %}
{% endif %}
{% endfor %}
</tbody>
{% endblock table.tbody %}
{% block table.tfoot %}
{% if table.has_footer %}
<tfoot {{ table.attrs.tfoot.as_html }}>
<tr>
{% for column in table.columns %}
<td {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>
{% endfor %}
</tr>
</tfoot>
{% endif %}
{% endblock table.tfoot %}
</table>
{% endblock table %}
{% block pagination %}
{% if table.page and table.paginator.num_pages > 1 %}
<nav aria-label="Table navigation">
<ul class="pagination justify-content-center flex-wrap">
{% if table.page.has_previous %}
{% block pagination.previous %}
<li class="previous page-item">
<a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}"
class="page-link">
<span aria-hidden="true">&laquo;</span>
{% trans 'previous' %}
</a>
</li>
{% endblock pagination.previous %}
{% endif %}
{% if table.page.has_previous or table.page.has_next %}
{% block pagination.range %}
{% for p in table.page|table_page_range:table.paginator %}
<li class="page-item{% if table.page.number == p %} active{% endif %}">
<a class="page-link"
{% if p != '...' %}href="{% querystring table.prefixed_page_field=p %}"{% endif %}>
{{ p }}
</a>
</li>
{% endfor %}
{% endblock pagination.range %}
{% endif %}
{% if table.page.has_next %}
{% block pagination.next %}
<li class="next page-item">
<a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}"
class="page-link">
{% trans 'next' %}
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endblock pagination.next %}
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock pagination %}
</div>
{% endblock content %}

View File

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% load i18n %}
{% load django_tables2 %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="table-container">
<h3 style="margin-bottom: 2vh">{% trans 'Manage Keywords' %} {% trans 'List' %}
{% if create_url %}
<a href="{% url create_url %}"> <i class="fas fa-plus-circle"></i>
</a>
{% endif %}
</h3>
{% render_table table %}
</div>
{% endblock content %}

View File

@ -11,7 +11,7 @@ from cookbook.helper import dal
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe, from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList,
Storage, Sync, SyncLog, get_model_name) 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 = routers.DefaultRouter()
router.register(r'user-name', api.UserNameViewSet, basename='username') router.register(r'user-name', api.UserNameViewSet, basename='username')
@ -48,7 +48,8 @@ urlpatterns = [
path('plan/entry/<int:pk>', views.meal_plan_entry, name='view_plan_entry'), path('plan/entry/<int:pk>', 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/<int:pk>', views.shopping_list, name='view_shopping'), path('shopping/<int:pk>', 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('settings/', views.user_settings, name='view_settings'),
path('history/', views.history, name='view_history'), path('history/', views.history, name='view_history'),
path('test/', views.test, name='view_test'), path('test/', views.test, name='view_test'),
@ -58,22 +59,30 @@ urlpatterns = [
path('export/', import_export.export_recipe, name='view_export'), path('export/', import_export.export_recipe, name='view_export'),
path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'), path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'),
path('view/recipe/<int:pk>/<slug:share>', views.recipe_view, name='view_recipe'), path('view/recipe/<int:pk>/<slug:share>',
views.recipe_view, name='view_recipe'),
path('new/recipe-import/<int:import_id>/', new.create_new_external_recipe, name='new_recipe_import'), path('new/recipe-import/<int:import_id>/',
new.create_new_external_recipe, name='new_recipe_import'),
path('new/share-link/<int:pk>/', new.share_link, name='new_share_link'), path('new/share-link/<int:pk>/', new.share_link, name='new_share_link'),
path('edit/recipe/<int:pk>/', edit.switch_recipe, name='edit_recipe'), path('edit/recipe/<int:pk>/', edit.switch_recipe, name='edit_recipe'),
path('manage/keywords/', manage.keywords, name='manage_keywords'),
# for internal use only # for internal use only
path('edit/recipe/internal/<int:pk>/', edit.internal_recipe_update, name='edit_internal_recipe'), path('edit/recipe/internal/<int:pk>/',
path('edit/recipe/external/<int:pk>/', edit.ExternalRecipeUpdate.as_view(), name='edit_external_recipe'), edit.internal_recipe_update, name='edit_internal_recipe'),
path('edit/recipe/convert/<int:pk>/', edit.convert_recipe, name='edit_convert_recipe'), path('edit/recipe/external/<int:pk>/',
edit.ExternalRecipeUpdate.as_view(), name='edit_external_recipe'),
path('edit/recipe/convert/<int:pk>/',
edit.convert_recipe, name='edit_convert_recipe'),
path('edit/storage/<int:pk>/', edit.edit_storage, name='edit_storage'), path('edit/storage/<int:pk>/', edit.edit_storage, name='edit_storage'),
path('edit/ingredient/', edit.edit_ingredients, name='edit_food'), path('edit/ingredient/', edit.edit_ingredients, name='edit_food'),
path('delete/recipe-source/<int:pk>/', delete.delete_recipe_source, name='delete_recipe_source'), path('delete/recipe-source/<int:pk>/',
delete.delete_recipe_source, name='delete_recipe_source'),
# TODO move to generic "new" view # TODO move to generic "new" view
path('data/sync', data.sync, name='data_sync'), path('data/sync', data.sync, name='data_sync'),
@ -83,14 +92,19 @@ urlpatterns = [
path('data/statistics', data.statistics, name='data_stats'), path('data/statistics', data.statistics, name='data_stats'),
path('data/import/url', data.import_url, name='data_import_url'), path('data/import/url', data.import_url, name='data_import_url'),
path('api/get_external_file_link/<int:recipe_id>/', api.get_external_file_link, name='api_get_external_file_link'), path('api/get_external_file_link/<int:recipe_id>/',
path('api/get_recipe_file/<int:recipe_id>/', api.get_recipe_file, name='api_get_recipe_file'), api.get_external_file_link, name='api_get_external_file_link'),
path('api/get_recipe_file/<int:recipe_id>/',
api.get_recipe_file, name='api_get_recipe_file'),
path('api/sync_all/', api.sync_all, name='api_sync'), path('api/sync_all/', api.sync_all, name='api_sync'),
path('api/log_cooking/<int:recipe_id>/', api.log_cooking, name='api_log_cooking'), path('api/log_cooking/<int:recipe_id>/',
path('api/plan-ical/<slug:from_date>/<slug:to_date>/', api.get_plan_ical, name='api_get_plan_ical'), api.log_cooking, name='api_log_cooking'),
path('api/plan-ical/<slug:from_date>/<slug:to_date>/',
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/recipe-from-url/', api.recipe_from_url, name='api_recipe_from_url'),
path('api/backup/', api.get_backup, name='api_backup'), 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/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'), 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/markdown/', views.markdown_info, name='docs_markdown'),
path('docs/api/', views.api_info, name='docs_api'), 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/', include((router.urls, 'api'))),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
@ -107,8 +122,10 @@ urlpatterns = [
path('offline/', views.offline, name='view_offline'), 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('service-worker.js', (TemplateView.as_view(template_name="sw.js",
path('manifest.json', (TemplateView.as_view(template_name="manifest.json", content_type='application/json', )), name='web_manifest'), 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 = ( generic_models = (

View File

@ -6,6 +6,7 @@ import cookbook.views.import_export
import cookbook.views.lists import cookbook.views.lists
import cookbook.views.new import cookbook.views.new
import cookbook.views.views import cookbook.views.views
import cookbook.views.manage
__all__ = [ __all__ = [
'api', 'api',
@ -14,6 +15,7 @@ __all__ = [
'edit', 'edit',
'import_export', 'import_export',
'lists', 'lists',
'manage',
'new', 'new',
'views', 'views',
] ]

19
cookbook/views/manage.py Normal file
View File

@ -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'}
)