Added caching and initial settings
This commit is contained in:
parent
a9bb4a7122
commit
d6eb16a398
@ -1,11 +1,15 @@
|
|||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from core.decorators import per_identity_cache_page
|
||||||
from users.decorators import identity_required
|
from users.decorators import identity_required
|
||||||
from users.models import FollowStates
|
from users.models import FollowStates
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(identity_required, name="dispatch")
|
@method_decorator(identity_required, name="dispatch")
|
||||||
|
@method_decorator(
|
||||||
|
per_identity_cache_page("cache_timeout_page_timeline"), name="dispatch"
|
||||||
|
)
|
||||||
class FollowsPage(TemplateView):
|
class FollowsPage(TemplateView):
|
||||||
"""
|
"""
|
||||||
Shows followers/follows.
|
Shows followers/follows.
|
||||||
|
@ -6,11 +6,13 @@ from django.utils.decorators import method_decorator
|
|||||||
from django.views.generic import TemplateView, View
|
from django.views.generic import TemplateView, View
|
||||||
|
|
||||||
from activities.models import Post, PostInteraction, PostInteractionStates, PostStates
|
from activities.models import Post, PostInteraction, PostInteractionStates, PostStates
|
||||||
|
from core.decorators import per_identity_cache_page
|
||||||
from core.ld import canonicalise
|
from core.ld import canonicalise
|
||||||
from users.decorators import identity_required
|
from users.decorators import identity_required
|
||||||
from users.shortcuts import by_handle_or_404
|
from users.shortcuts import by_handle_or_404
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(per_identity_cache_page("cache_timeout_page_post"), name="dispatch")
|
||||||
class Individual(TemplateView):
|
class Individual(TemplateView):
|
||||||
|
|
||||||
template_name = "activities/post.html"
|
template_name = "activities/post.html"
|
||||||
|
@ -5,11 +5,13 @@ from django.utils.decorators import method_decorator
|
|||||||
from django.views.generic import FormView, ListView
|
from django.views.generic import FormView, ListView
|
||||||
|
|
||||||
from activities.models import Hashtag, Post, PostInteraction, TimelineEvent
|
from activities.models import Hashtag, Post, PostInteraction, TimelineEvent
|
||||||
|
from core.decorators import per_identity_cache_page
|
||||||
from core.models import Config
|
from core.models import Config
|
||||||
from users.decorators import identity_required
|
from users.decorators import identity_required
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(identity_required, name="dispatch")
|
@method_decorator(identity_required, name="dispatch")
|
||||||
|
@method_decorator(per_identity_cache_page(), name="dispatch")
|
||||||
class Home(FormView):
|
class Home(FormView):
|
||||||
|
|
||||||
template_name = "activities/home.html"
|
template_name = "activities/home.html"
|
||||||
@ -61,6 +63,9 @@ class Home(FormView):
|
|||||||
return redirect(".")
|
return redirect(".")
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(
|
||||||
|
per_identity_cache_page("cache_timeout_page_timeline"), name="dispatch"
|
||||||
|
)
|
||||||
class Tag(ListView):
|
class Tag(ListView):
|
||||||
|
|
||||||
template_name = "activities/tag.html"
|
template_name = "activities/tag.html"
|
||||||
@ -96,6 +101,9 @@ class Tag(ListView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(
|
||||||
|
per_identity_cache_page("cache_timeout_page_timeline"), name="dispatch"
|
||||||
|
)
|
||||||
class Local(ListView):
|
class Local(ListView):
|
||||||
|
|
||||||
template_name = "activities/local.html"
|
template_name = "activities/local.html"
|
||||||
@ -122,6 +130,9 @@ class Local(ListView):
|
|||||||
|
|
||||||
|
|
||||||
@method_decorator(identity_required, name="dispatch")
|
@method_decorator(identity_required, name="dispatch")
|
||||||
|
@method_decorator(
|
||||||
|
per_identity_cache_page("cache_timeout_page_timeline"), name="dispatch"
|
||||||
|
)
|
||||||
class Federated(ListView):
|
class Federated(ListView):
|
||||||
|
|
||||||
template_name = "activities/federated.html"
|
template_name = "activities/federated.html"
|
||||||
@ -150,6 +161,9 @@ class Federated(ListView):
|
|||||||
|
|
||||||
|
|
||||||
@method_decorator(identity_required, name="dispatch")
|
@method_decorator(identity_required, name="dispatch")
|
||||||
|
@method_decorator(
|
||||||
|
per_identity_cache_page("cache_timeout_page_timeline"), name="dispatch"
|
||||||
|
)
|
||||||
class Notifications(ListView):
|
class Notifications(ListView):
|
||||||
|
|
||||||
template_name = "activities/notifications.html"
|
template_name = "activities/notifications.html"
|
||||||
|
41
core/decorators.py
Normal file
41
core/decorators.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from functools import partial, wraps
|
||||||
|
|
||||||
|
from django.views.decorators.cache import cache_page as dj_cache_page
|
||||||
|
|
||||||
|
from core.models import Config
|
||||||
|
|
||||||
|
|
||||||
|
def cache_page(
|
||||||
|
timeout: int | str = "cache_timeout_page_default",
|
||||||
|
*,
|
||||||
|
per_identity: bool = False,
|
||||||
|
key_prefix: str = "",
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Decorator for views that caches the page result.
|
||||||
|
timeout can either be the number of seconds or the name of a SystemOptions
|
||||||
|
value.
|
||||||
|
"""
|
||||||
|
if isinstance(timeout, str):
|
||||||
|
timeout = Config.lazy_system_value(timeout)
|
||||||
|
|
||||||
|
def decorator(function):
|
||||||
|
@wraps(function)
|
||||||
|
def inner(request, *args, **kwargs):
|
||||||
|
prefix = key_prefix
|
||||||
|
if per_identity:
|
||||||
|
identity_id = request.identity.pk if request.identity else "0"
|
||||||
|
prefix = f"{key_prefix or ''}:ident{identity_id}"
|
||||||
|
_timeout = timeout
|
||||||
|
if callable(_timeout):
|
||||||
|
_timeout = _timeout()
|
||||||
|
return dj_cache_page(timeout=_timeout, key_prefix=prefix)(function)(
|
||||||
|
request, *args, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
per_identity_cache_page = partial(cache_page, per_identity=True)
|
@ -1,3 +1,5 @@
|
|||||||
|
from time import time
|
||||||
|
|
||||||
from django.core.exceptions import MiddlewareNotUsed
|
from django.core.exceptions import MiddlewareNotUsed
|
||||||
|
|
||||||
from core import sentry
|
from core import sentry
|
||||||
@ -9,11 +11,19 @@ class ConfigLoadingMiddleware:
|
|||||||
Caches the system config every request
|
Caches the system config every request
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
refresh_interval: float = 30.0
|
||||||
|
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
self.config_ts: float = 0.0
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
|
if (
|
||||||
|
not getattr(Config, "system", None)
|
||||||
|
or (time() - self.config_ts) >= self.refresh_interval
|
||||||
|
):
|
||||||
Config.system = Config.load_system()
|
Config.system = Config.load_system()
|
||||||
|
self.config_ts = time()
|
||||||
response = self.get_response(request)
|
response = self.get_response(request)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -218,6 +218,11 @@ class Config(models.Model):
|
|||||||
hashtag_unreviewed_are_public: bool = True
|
hashtag_unreviewed_are_public: bool = True
|
||||||
hashtag_stats_max_age: int = 60 * 60
|
hashtag_stats_max_age: int = 60 * 60
|
||||||
|
|
||||||
|
cache_timeout_page_default: int = 60
|
||||||
|
cache_timeout_page_timeline: int = 60 * 3
|
||||||
|
cache_timeout_page_post: int = 60 * 2
|
||||||
|
cache_timeout_identity_feed: int = 60 * 5
|
||||||
|
|
||||||
restricted_usernames: str = "admin\nadmins\nadministrator\nadministrators\nsystem\nroot\nannounce\nannouncement\nannouncements"
|
restricted_usernames: str = "admin\nadmins\nadministrator\nadministrators\nsystem\nroot\nannounce\nannouncement\nannouncements"
|
||||||
|
|
||||||
class UserOptions(pydantic.BaseModel):
|
class UserOptions(pydantic.BaseModel):
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.generic import TemplateView, View
|
from django.views.generic import TemplateView, View
|
||||||
|
|
||||||
from activities.views.timelines import Home
|
from activities.views.timelines import Home
|
||||||
|
from core.decorators import cache_page
|
||||||
from users.models import Identity
|
from users.models import Identity
|
||||||
|
|
||||||
|
|
||||||
@ -13,6 +15,7 @@ def homepage(request):
|
|||||||
return LoggedOutHomepage.as_view()(request)
|
return LoggedOutHomepage.as_view()(request)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(cache_page(), name="dispatch")
|
||||||
class LoggedOutHomepage(TemplateView):
|
class LoggedOutHomepage(TemplateView):
|
||||||
|
|
||||||
template_name = "index.html"
|
template_name = "index.html"
|
||||||
|
@ -14,3 +14,25 @@ Environment Variable:
|
|||||||
making remote requests to other Fediverse instances. This may also be a
|
making remote requests to other Fediverse instances. This may also be a
|
||||||
tuple of four floats to set the timeouts for connect, read, write, and
|
tuple of four floats to set the timeouts for connect, read, write, and
|
||||||
pool. Example ``TAKAHE_REMOTE_TIMEOUT='[0.5, 1.0, 1.0, 0.5]'``
|
pool. Example ``TAKAHE_REMOTE_TIMEOUT='[0.5, 1.0, 1.0, 0.5]'``
|
||||||
|
|
||||||
|
|
||||||
|
Caching
|
||||||
|
--------
|
||||||
|
|
||||||
|
By default Takakē has caching disabled. The caching needs of a server can
|
||||||
|
varying drastically based upon the number of users and how interconnected
|
||||||
|
they are with other servers.
|
||||||
|
|
||||||
|
Caching is configured by specifying a cache DSN in the environment variable
|
||||||
|
``TAKAHE_CACHES_DEFAULT``. The DSN format can be any supported by
|
||||||
|
`django-cache-url <https://github.com/epicserve/django-cache-url>`_, but
|
||||||
|
some cache backends will require additional Python pacakages not required
|
||||||
|
by Takahē.
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
* LocMem cache for a small server: ``locmem://default``
|
||||||
|
* Memcache cache for a service named ``memcache`` in a docker compose file:
|
||||||
|
``memcached://memcache:11211?key_prefix=takahe``
|
||||||
|
* Multiple memcache cache servers:
|
||||||
|
``memcached://server1:11211,server2:11211``
|
||||||
|
@ -2,6 +2,7 @@ bleach~=5.0.1
|
|||||||
blurhash-python~=1.1.3
|
blurhash-python~=1.1.3
|
||||||
cryptography~=38.0
|
cryptography~=38.0
|
||||||
dj_database_url~=1.0.0
|
dj_database_url~=1.0.0
|
||||||
|
django-cache-url~=3.4.2
|
||||||
django-htmx~=1.13.0
|
django-htmx~=1.13.0
|
||||||
django-storages[google,boto3]~=1.13.1
|
django-storages[google,boto3]~=1.13.1
|
||||||
django~=4.1
|
django~=4.1
|
||||||
|
@ -6,6 +6,7 @@ from pathlib import Path
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
import dj_database_url
|
import dj_database_url
|
||||||
|
import django_cache_url
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
from pydantic import AnyUrl, BaseSettings, EmailStr, Field, validator
|
from pydantic import AnyUrl, BaseSettings, EmailStr, Field, validator
|
||||||
from sentry_sdk.integrations.django import DjangoIntegration
|
from sentry_sdk.integrations.django import DjangoIntegration
|
||||||
@ -15,6 +16,11 @@ from takahe import __version__
|
|||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
class CacheBackendUrl(AnyUrl):
|
||||||
|
host_required = False
|
||||||
|
allowed_schemes = django_cache_url.BACKENDS.keys()
|
||||||
|
|
||||||
|
|
||||||
class ImplicitHostname(AnyUrl):
|
class ImplicitHostname(AnyUrl):
|
||||||
host_required = False
|
host_required = False
|
||||||
|
|
||||||
@ -107,6 +113,9 @@ class Settings(BaseSettings):
|
|||||||
#: (placeholder setting, no effect)
|
#: (placeholder setting, no effect)
|
||||||
SEARCH: bool = True
|
SEARCH: bool = True
|
||||||
|
|
||||||
|
#: Default cache backend
|
||||||
|
CACHES_DEFAULT: CacheBackendUrl | None = None
|
||||||
|
|
||||||
PGHOST: str | None = None
|
PGHOST: str | None = None
|
||||||
PGPORT: int | None = 5432
|
PGPORT: int | None = 5432
|
||||||
PGNAME: str = "takahe"
|
PGNAME: str = "takahe"
|
||||||
@ -339,5 +348,7 @@ if SETUP.MEDIA_BACKEND:
|
|||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported media backend {parsed.scheme}")
|
raise ValueError(f"Unsupported media backend {parsed.scheme}")
|
||||||
|
|
||||||
|
CACHES = {"default": django_cache_url.parse(SETUP.CACHES_DEFAULT or "dummy://")}
|
||||||
|
|
||||||
if SETUP.ERROR_EMAILS:
|
if SETUP.ERROR_EMAILS:
|
||||||
ADMINS = [("Admin", e) for e in SETUP.ERROR_EMAILS]
|
ADMINS = [("Admin", e) for e in SETUP.ERROR_EMAILS]
|
||||||
|
@ -55,6 +55,11 @@ urlpatterns = [
|
|||||||
admin.BasicSettings.as_view(),
|
admin.BasicSettings.as_view(),
|
||||||
name="admin_basic",
|
name="admin_basic",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"admin/tuning/",
|
||||||
|
admin.TuningSettings.as_view(),
|
||||||
|
name="admin_tuning",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"admin/domains/",
|
"admin/domains/",
|
||||||
admin.Domains.as_view(),
|
admin.Domains.as_view(),
|
||||||
|
@ -36,6 +36,9 @@
|
|||||||
<a href="{% url "admin_hashtags" %}" {% if section == "hashtags" %}class="selected"{% endif %} title="Hashtags">
|
<a href="{% url "admin_hashtags" %}" {% if section == "hashtags" %}class="selected"{% endif %} title="Hashtags">
|
||||||
<i class="fa-solid fa-hashtag"></i> Hashtags
|
<i class="fa-solid fa-hashtag"></i> Hashtags
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url "admin_tuning" %}" {% if section == "tuning" %}class="selected"{% endif %} title="Tuning">
|
||||||
|
<i class="fa-solid fa-wrench"></i> Tuning
|
||||||
|
</a>
|
||||||
<a href="/djadmin" title="">
|
<a href="/djadmin" title="">
|
||||||
<i class="fa-solid fa-gear"></i> Django Admin
|
<i class="fa-solid fa-gear"></i> Django Admin
|
||||||
</a>
|
</a>
|
||||||
|
@ -9,6 +9,7 @@ from django.views.generic import View
|
|||||||
|
|
||||||
from activities.models import Post
|
from activities.models import Post
|
||||||
from core import exceptions
|
from core import exceptions
|
||||||
|
from core.decorators import cache_page
|
||||||
from core.ld import canonicalise
|
from core.ld import canonicalise
|
||||||
from core.models import Config
|
from core.models import Config
|
||||||
from core.signatures import (
|
from core.signatures import (
|
||||||
@ -61,6 +62,7 @@ class NodeInfo(View):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(cache_page(), name="dispatch")
|
||||||
class NodeInfo2(View):
|
class NodeInfo2(View):
|
||||||
"""
|
"""
|
||||||
Returns the nodeinfo 2.0 response
|
Returns the nodeinfo 2.0 response
|
||||||
@ -87,6 +89,7 @@ class NodeInfo2(View):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(cache_page(), name="dispatch")
|
||||||
class Webfinger(View):
|
class Webfinger(View):
|
||||||
"""
|
"""
|
||||||
Services webfinger requests
|
Services webfinger requests
|
||||||
@ -189,6 +192,7 @@ class Inbox(View):
|
|||||||
return HttpResponse(status=202)
|
return HttpResponse(status=202)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(cache_page(), name="dispatch")
|
||||||
class SystemActorView(View):
|
class SystemActorView(View):
|
||||||
"""
|
"""
|
||||||
Special endpoint for the overall system actor
|
Special endpoint for the overall system actor
|
||||||
|
@ -17,7 +17,7 @@ from users.views.admin.hashtags import ( # noqa
|
|||||||
HashtagEdit,
|
HashtagEdit,
|
||||||
Hashtags,
|
Hashtags,
|
||||||
)
|
)
|
||||||
from users.views.admin.settings import BasicSettings # noqa
|
from users.views.admin.settings import BasicSettings, TuningSettings # noqa
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(admin_required, name="dispatch")
|
@method_decorator(admin_required, name="dispatch")
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from core.models import Config
|
from core.models import Config
|
||||||
from users.decorators import admin_required
|
from users.decorators import admin_required
|
||||||
@ -106,3 +107,51 @@ class BasicSettings(AdminSettingsPage):
|
|||||||
"restricted_usernames",
|
"restricted_usernames",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cache_field_defaults = {
|
||||||
|
"min_value": 0,
|
||||||
|
"max_value": 900,
|
||||||
|
"step_size": 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TuningSettings(AdminSettingsPage):
|
||||||
|
|
||||||
|
section = "tuning"
|
||||||
|
|
||||||
|
options = {
|
||||||
|
"cache_timeout_page_default": {
|
||||||
|
**cache_field_defaults,
|
||||||
|
"title": "Default Timeout",
|
||||||
|
"help_text": "The number of seconds to cache a rendered page",
|
||||||
|
},
|
||||||
|
"cache_timeout_page_timeline": {
|
||||||
|
**cache_field_defaults,
|
||||||
|
"title": "Timeline Timeout",
|
||||||
|
"help_text": "The number of seconds to cache a rendered timeline page",
|
||||||
|
},
|
||||||
|
"cache_timeout_page_post": {
|
||||||
|
**cache_field_defaults,
|
||||||
|
"title": "Individual Post Timeout",
|
||||||
|
"help_text": mark_safe(
|
||||||
|
"The number of seconds to cache a rendered individual Post page<br>Note: This includes the JSON responses to other servers"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"cache_timeout_identity_feed": {
|
||||||
|
**cache_field_defaults,
|
||||||
|
"title": "Identity Feed Timeout",
|
||||||
|
"help_text": "The number of seconds to cache a rendered Identity RSS feed",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
layout = {
|
||||||
|
"Rendered Page Cache": [
|
||||||
|
"cache_timeout_page_default",
|
||||||
|
"cache_timeout_page_timeline",
|
||||||
|
"cache_timeout_page_post",
|
||||||
|
],
|
||||||
|
"RSS Feeds": [
|
||||||
|
"cache_timeout_identity_feed",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ from django.utils.decorators import method_decorator
|
|||||||
from django.views.generic import FormView, ListView, TemplateView, View
|
from django.views.generic import FormView, ListView, TemplateView, View
|
||||||
|
|
||||||
from activities.models import Post, PostInteraction
|
from activities.models import Post, PostInteraction
|
||||||
|
from core.decorators import per_identity_cache_page
|
||||||
from core.ld import canonicalise
|
from core.ld import canonicalise
|
||||||
from core.models import Config
|
from core.models import Config
|
||||||
from users.decorators import identity_required
|
from users.decorators import identity_required
|
||||||
@ -17,6 +18,7 @@ from users.models import Domain, Follow, FollowStates, Identity, IdentityStates
|
|||||||
from users.shortcuts import by_handle_or_404
|
from users.shortcuts import by_handle_or_404
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(per_identity_cache_page(), name="dispatch")
|
||||||
class ViewIdentity(ListView):
|
class ViewIdentity(ListView):
|
||||||
"""
|
"""
|
||||||
Shows identity profile pages, and also acts as the Actor endpoint when
|
Shows identity profile pages, and also acts as the Actor endpoint when
|
||||||
@ -90,6 +92,9 @@ class ViewIdentity(ListView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(
|
||||||
|
per_identity_cache_page("cache_timeout_identity_feed"), name="__call__"
|
||||||
|
)
|
||||||
class IdentityFeed(Feed):
|
class IdentityFeed(Feed):
|
||||||
"""
|
"""
|
||||||
Serves a local user's Public posts as an RSS feed
|
Serves a local user's Public posts as an RSS feed
|
||||||
|
@ -21,7 +21,7 @@ class SettingsPage(FormView):
|
|||||||
options_class = Config.IdentityOptions
|
options_class = Config.IdentityOptions
|
||||||
template_name = "settings/settings.html"
|
template_name = "settings/settings.html"
|
||||||
section: ClassVar[str]
|
section: ClassVar[str]
|
||||||
options: dict[str, dict[str, str]]
|
options: dict[str, dict[str, str | int]]
|
||||||
layout: dict[str, list[str]]
|
layout: dict[str, list[str]]
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
@ -51,6 +51,10 @@ class SettingsPage(FormView):
|
|||||||
choices = details.get("choices")
|
choices = details.get("choices")
|
||||||
if choices:
|
if choices:
|
||||||
field_kwargs["widget"] = forms.Select(choices=choices)
|
field_kwargs["widget"] = forms.Select(choices=choices)
|
||||||
|
for int_kwarg in {"min_value", "max_value", "step_size"}:
|
||||||
|
val = details.get(int_kwarg)
|
||||||
|
if val:
|
||||||
|
field_kwargs[int_kwarg] = val
|
||||||
form_field = forms.IntegerField
|
form_field = forms.IntegerField
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Cannot render settings type {config_field.type_}")
|
raise ValueError(f"Cannot render settings type {config_field.type_}")
|
||||||
|
Reference in New Issue
Block a user