From 5b6dd62f8e3fa8609b26c3451b943c78b76414f6 Mon Sep 17 00:00:00 2001 From: smilerz Date: Tue, 23 Nov 2021 19:18:10 -0600 Subject: [PATCH] generic modal refactored --- .gitignore | 1 + cookbook/models.py | 36 +- cookbook/schemas.py | 111 +- cookbook/templates/base.html | 4 + cookbook/templates/settings.html | 6 +- cookbook/templates/shopping_list.html | 9 +- cookbook/templates/url_import.html | 1 + cookbook/templatetags/custom_tags.py | 30 +- cookbook/tests/api/test_api_step.py | 12 +- cookbook/views/api.py | 106 +- vue/src/apps/ModelListView/ModelListView.vue | 884 ++++++++-------- vue/src/components/GenericPill.vue | 69 +- .../components/Modals/GenericModalForm.vue | 367 ++++--- vue/src/components/Modals/LookupInput.vue | 302 +++--- vue/src/locales/en.json | 423 ++++---- vue/src/utils/models.js | 952 +++++++++--------- vue/src/utils/utils.js | 443 ++++---- 17 files changed, 1951 insertions(+), 1805 deletions(-) diff --git a/.gitignore b/.gitignore index 5791432e..e36efacb 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,4 @@ vetur.config.js cookbook/static/vue vue/webpack-stats.json cookbook/templates/sw.js +.prettierignore diff --git a/cookbook/models.py b/cookbook/models.py index af2f6d85..aed927bd 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -2,25 +2,30 @@ import operator import pathlib import re import uuid +from collections import OrderedDict from datetime import date, timedelta +from decimal import Decimal from annoying.fields import AutoOneToOneField from django.contrib import auth from django.contrib.auth.models import Group, User from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.search import SearchVectorField -from django.core.files.uploadedfile import UploadedFile, InMemoryUploadedFile +from django.core.files.uploadedfile import InMemoryUploadedFile, UploadedFile from django.core.validators import MinLengthValidator -from django.db import models, IntegrityError -from django.db.models import Index, ProtectedError +from django.db import IntegrityError, models +from django.db.models import Index, ProtectedError, Q, Subquery +from django.db.models.fields.related import ManyToManyField +from django.db.models.functions import Substr +from django.db.transaction import atomic from django.utils import timezone from django.utils.translation import gettext as _ -from treebeard.mp_tree import MP_Node, MP_NodeManager -from django_scopes import ScopedManager, scopes_disabled from django_prometheus.models import ExportModelOperationsMixin -from recipes.settings import (COMMENT_PREF_DEFAULT, FRACTION_PREF_DEFAULT, - KJ_PREF_DEFAULT, STICKY_NAV_PREF_DEFAULT, - SORT_TREE_BY_NAME) +from django_scopes import ScopedManager, scopes_disabled +from treebeard.mp_tree import MP_Node, MP_NodeManager + +from recipes.settings import (COMMENT_PREF_DEFAULT, FRACTION_PREF_DEFAULT, KJ_PREF_DEFAULT, + SORT_TREE_BY_NAME, STICKY_NAV_PREF_DEFAULT) def get_user_name(self): @@ -38,15 +43,26 @@ def get_model_name(model): class TreeManager(MP_NodeManager): + def create(self, *args, **kwargs): + return self.get_or_create(*args, **kwargs)[0] + # model.Manager get_or_create() is not compatible with MP_Tree - def get_or_create(self, **kwargs): + def get_or_create(self, *args, **kwargs): kwargs['name'] = kwargs['name'].strip() try: return self.get(name__exact=kwargs['name'], space=kwargs['space']), False except self.model.DoesNotExist: with scopes_disabled(): try: - return self.model.add_root(**kwargs), True + # ManyToMany fields can't be set this way, so pop them out to save for later + fields = [field.name for field in self.model._meta.get_fields() if issubclass(type(field), ManyToManyField)] + many_to_many = {field: kwargs.pop(field) for field in list(kwargs) if field in fields} + obj = self.model.add_root(**kwargs) + for field in many_to_many: + field_model = getattr(obj, field).model + for related_obj in many_to_many[field]: + getattr(obj, field).add(field_model.objects.get(**dict(related_obj))) + return obj, True except IntegrityError as e: if 'Key (path)' in e.args[0]: self.model.fix_tree(fix_paths=True) diff --git a/cookbook/schemas.py b/cookbook/schemas.py index fc8ded6c..36ce6655 100644 --- a/cookbook/schemas.py +++ b/cookbook/schemas.py @@ -2,78 +2,29 @@ from rest_framework.schemas.openapi import AutoSchema from rest_framework.schemas.utils import is_list_view -# TODO move to separate class to cleanup -class RecipeSchema(AutoSchema): +class QueryParam(object): + def __init__(self, name, description=None, qtype='string', required=False): + self.name = name + self.description = description + self.qtype = qtype + self.required = required + + def __str__(self): + return f'{self.name}, {self.qtype}, {self.description}' + + +class QueryParamAutoSchema(AutoSchema): def get_path_parameters(self, path, method): if not is_list_view(path, method, self.view): - return super(RecipeSchema, self).get_path_parameters(path, method) - + return super().get_path_parameters(path, method) parameters = super().get_path_parameters(path, method) - parameters.append({ - "name": 'query', "in": "query", "required": False, - "description": 'Query string matched (fuzzy) against recipe name. In the future also fulltext search.', - 'schema': {'type': 'string', }, - }) - parameters.append({ - "name": 'keywords', "in": "query", "required": False, - "description": 'Id of keyword a recipe should have. For multiple repeat parameter.', - 'schema': {'type': 'string', }, - }) - parameters.append({ - "name": 'foods', "in": "query", "required": False, - "description": 'Id of food a recipe should have. For multiple repeat parameter.', - 'schema': {'type': 'string', }, - }) - parameters.append({ - "name": 'units', "in": "query", "required": False, - "description": 'Id of unit a recipe should have.', - 'schema': {'type': 'int', }, - }) - parameters.append({ - "name": 'rating', "in": "query", "required": False, - "description": 'Id of unit a recipe should have.', - 'schema': {'type': 'int', }, - }) - parameters.append({ - "name": 'books', "in": "query", "required": False, - "description": 'Id of book a recipe should have. For multiple repeat parameter.', - 'schema': {'type': 'string', }, - }) - parameters.append({ - "name": 'steps', "in": "query", "required": False, - "description": 'Id of a step a recipe should have. For multiple repeat parameter.', - 'schema': {'type': 'string', }, - }) - parameters.append({ - "name": 'keywords_or', "in": "query", "required": False, - "description": 'If recipe should have all (AND) or any (OR) of the provided keywords.', - 'schema': {'type': 'string', }, - }) - parameters.append({ - "name": 'foods_or', "in": "query", "required": False, - "description": 'If recipe should have all (AND) or any (OR) any of the provided foods.', - 'schema': {'type': 'string', }, - }) - parameters.append({ - "name": 'books_or', "in": "query", "required": False, - "description": 'If recipe should be in all (AND) or any (OR) any of the provided books.', - 'schema': {'type': 'string', }, - }) - parameters.append({ - "name": 'internal', "in": "query", "required": False, - "description": 'true or false. If only internal recipes should be returned or not.', - 'schema': {'type': 'string', }, - }) - parameters.append({ - "name": 'random', "in": "query", "required": False, - "description": 'true or false. returns the results in randomized order.', - 'schema': {'type': 'string', }, - }) - parameters.append({ - "name": 'new', "in": "query", "required": False, - "description": 'true or false. returns new results first in search results', - 'schema': {'type': 'string', }, - }) + for q in self.view.query_params: + parameters.append({ + "name": q.name, "in": "query", "required": q.required, + "description": q.description, + 'schema': {'type': q.qtype, }, + }) + return parameters @@ -118,15 +69,15 @@ class FilterSchema(AutoSchema): return parameters -class QueryOnlySchema(AutoSchema): - def get_path_parameters(self, path, method): - if not is_list_view(path, method, self.view): - return super(QueryOnlySchema, self).get_path_parameters(path, method) +# class QueryOnlySchema(AutoSchema): +# def get_path_parameters(self, path, method): +# if not is_list_view(path, method, self.view): +# return super(QueryOnlySchema, self).get_path_parameters(path, method) - parameters = super().get_path_parameters(path, method) - parameters.append({ - "name": 'query', "in": "query", "required": False, - "description": 'Query string matched (fuzzy) against object name.', - 'schema': {'type': 'string', }, - }) - return parameters +# parameters = super().get_path_parameters(path, method) +# parameters.append({ +# "name": 'query', "in": "query", "required": False, +# "description": 'Query string matched (fuzzy) against object name.', +# 'schema': {'type': 'string', }, +# }) +# return parameters diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html index 6d7b0413..cbd77aa7 100644 --- a/cookbook/templates/base.html +++ b/cookbook/templates/base.html @@ -336,6 +336,10 @@ {% block content_fluid %} {% endblock %} + {% user_prefs request as prefs%} + {{ prefs|json_script:'user_preference' }} + + {% block script %} diff --git a/cookbook/templates/settings.html b/cookbook/templates/settings.html index 1f1e605c..ed591b9a 100644 --- a/cookbook/templates/settings.html +++ b/cookbook/templates/settings.html @@ -28,10 +28,10 @@ {% trans 'Account' %}