Skip to content

Commit

Permalink
add natural_keys
Browse files Browse the repository at this point in the history
  • Loading branch information
saxix committed Jul 2, 2024
1 parent 5876c17 commit 57ae51f
Show file tree
Hide file tree
Showing 54 changed files with 13,073 additions and 95 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@


## Install
CSP_SCRIPT_SRC = [
...
"cdnjs.cloudflare.com",
]

INSTALLED_APPS = [
...
Expand Down
29 changes: 28 additions & 1 deletion pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies = [
"django-regex-field>=3.1.0",
"django-admin-extra-buttons>=1.5.8",
"django-jsoneditor>=0.2.4",
"djangorestframework>=3.15.1",
]
requires-python = ">=3.11"
readme = "README.md"
Expand Down Expand Up @@ -70,6 +71,7 @@ dev = [
"pytest-cov>=5.0.0",
"django-webtest>=1.9.11",
"responses>=0.25.3",
"django-csp>=3.8",
]

[tool.pdm.scripts]
Expand Down
33 changes: 19 additions & 14 deletions src/hope_flex_fields/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,36 @@
from admin_extra_buttons.mixins import ExtraButtonsMixin
from jsoneditor.forms import JSONEditor

from .forms import FieldsetForm
from .models import FieldDefinition, Fieldset, FieldsetField


@register(FieldDefinition)
class FieldDefinitionAdmin(ExtraButtonsMixin, ModelAdmin):
list_display = ("name", "description", "field_type", "required")
formfield_overrides = {
JSONField: {
"widget": JSONEditor(
init_options={"mode": "code", "modes": ["text", "code", "tree"]},
ace_options={"readOnly": False},
)
}
}
form = FieldsetForm

@button()
def test(self, request, pk):
ctx = self.get_common_context(request, pk)
ctx = self.get_common_context(request, pk, title="Test")
fd: FieldDefinition = ctx["original"]
field = fd.get_field()
form_class_attrs = {
fd.name: field,
}
flexForm = type("TestFlexForm", (forms.Form,), form_class_attrs)
ctx["form"] = flexForm
form_class = type("TestFlexForm", (forms.Form,), form_class_attrs)
if request.method == "POST":
form = form_class(request.POST)
if form.is_valid():
self.message_user(request, "Valid", messages.SUCCESS)
else:
self.message_user(
request, "Please correct the errors below", messages.ERROR
)
else:
form = form_class()

ctx["form"] = form
return render(request, "flex_fields/fielddefinition/test.html", ctx)

@button()
Expand All @@ -46,7 +51,7 @@ def inspect(self, request, pk):
class FieldsetFieldTabularInline(TabularInline):
model = FieldsetField
fields = (
"label",
"name",
"field",
)

Expand All @@ -58,7 +63,7 @@ class FieldsetAdmin(ExtraButtonsMixin, ModelAdmin):

@button()
def test(self, request, pk):
ctx = self.get_common_context(request, pk)
ctx = self.get_common_context(request, pk, title="Test")
fs: Fieldset = ctx["original"]
form_class = fs.get_form()
if request.method == "POST":
Expand All @@ -78,7 +83,7 @@ def test(self, request, pk):

@register(FieldsetField)
class FieldsetFieldAdmin(ExtraButtonsMixin, ModelAdmin):
list_display = ("fieldset", "field", "label")
list_display = ("fieldset", "field", "name")
formfield_overrides = {
JSONField: {
"widget": JSONEditor(
Expand Down
Empty file.
15 changes: 15 additions & 0 deletions src/hope_flex_fields/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from rest_framework import serializers
from strategy_field.utils import fqn

from hope_flex_fields.models import FieldDefinition


class FieldDefinitionSerializer(serializers.HyperlinkedModelSerializer):
field_type = serializers.SerializerMethodField()

class Meta:
model = FieldDefinition
fields = ["name", "description", "field_type", "attrs", "regex", "validation"]

def get_field_type(self, obj):
return fqn(obj)
10 changes: 10 additions & 0 deletions src/hope_flex_fields/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from rest_framework import permissions, viewsets

from ..models import FieldDefinition
from .serializers import FieldDefinitionSerializer


class FieldDefinitionViewSet(viewsets.ModelViewSet):
queryset = FieldDefinition.objects.all().order_by("pk")
serializer_class = FieldDefinitionSerializer
permission_classes = [permissions.IsAuthenticated]
21 changes: 19 additions & 2 deletions src/hope_flex_fields/forms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
from django import forms
from django.forms import ModelForm

from jsoneditor.forms import JSONEditor

class FieldsetForm(forms.Form):
fieldset = None
from .models import Fieldset
from .widgets import JavascriptEditor


class FieldsetForm(ModelForm):
validation = forms.CharField(widget=JavascriptEditor(toolbar=False), required=False)
attrs = forms.CharField(
widget=JSONEditor(
init_options={"mode": "code", "modes": ["text", "code", "tree"]},
ace_options={"readOnly": False},
),
required=False,
)

class Meta:
model = Fieldset
exclude = ()
47 changes: 0 additions & 47 deletions src/hope_flex_fields/migrations/0001_initial.py

This file was deleted.

58 changes: 48 additions & 10 deletions src/hope_flex_fields/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import inspect
import logging
from typing import cast
from typing import TYPE_CHECKING

from django import forms
from django.core.exceptions import ValidationError
Expand All @@ -10,9 +10,12 @@
from django_regex.validators import RegexValidator
from strategy_field.fields import StrategyClassField

from .forms import FieldsetForm
from .fields import FlexField
from .registry import field_registry

if TYPE_CHECKING:
from .forms import FieldsetForm

logger = logging.getLogger(__name__)

DEFAULT_ATTRS = {
Expand All @@ -22,17 +25,30 @@
}


class TestForm(forms.Form):
fieldset = None


class FieldDefinitionManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)


class FieldDefinition(models.Model):
name = models.CharField(max_length=255)
name = models.CharField(max_length=255, unique=True)
description = models.TextField(max_length=500, blank=True, null=True, default="")
field_type = StrategyClassField(registry=field_registry)
attrs = models.JSONField(default=DEFAULT_ATTRS, blank=True)
regex = RegexField(blank=True, null=True, validators=[RegexValidator()])
validation = models.TextField(blank=True, null=True)
objects = FieldDefinitionManager()

def __str__(self):
return self.name

def natural_key(self):
return (self.name,)

def clean(self):
try:
self.set_default_arguments()
Expand All @@ -55,33 +71,43 @@ def set_default_arguments(self):
def required(self):
return self.attrs.get("required", False)

def get_field(self) -> forms.Field:
def get_field(self) -> "FlexField":
try:
kwargs = dict(self.attrs)
fld = cast(forms.Field, self.field_type(**kwargs)) # noqa
field_class = type(f"{self.name}Field", (FlexField, self.field_type), {})
fld = field_class(**kwargs)
except Exception as e: # pragma: no cover
logger.exception(e)
raise
return fld


class FieldsetManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)


class Fieldset(models.Model):
name = models.CharField(max_length=255)
name = models.CharField(max_length=255, unique=True)
objects = FieldsetManager()

def __str__(self):
return self.name

def get_form(self) -> type[FieldsetForm]:
def natural_key(self):
return (self.name,)

def get_form(self) -> "type[FieldsetForm]":
fields: dict[str, forms.Field] = {}
field: FieldsetField
for field in self.fields.filter():
fld = field.get_field()
fields[field.label] = fld
fields[field.name] = fld
form_class_attrs = {
"FieldsetForm": self,
**fields,
}
return type(f"{self.name}FieldsetForm", (FieldsetForm,), form_class_attrs)
return type(f"{self.name}FieldsetForm", (TestForm,), form_class_attrs)

def validate(self, data):
form_class = self.get_form()
Expand All @@ -92,15 +118,27 @@ def validate(self, data):
raise ValidationError(form.errors)


class FieldsetFieldManager(models.Manager):
def get_by_natural_key(self, name, fieldset_name):
return self.get(name=name, fieldset__name=fieldset_name)


class FieldsetField(models.Model):
label = models.CharField(max_length=255)
name = models.CharField(max_length=255, unique=True)
fieldset = models.ForeignKey(
Fieldset, on_delete=models.CASCADE, related_name="fields"
)
field = models.ForeignKey(
FieldDefinition, on_delete=models.CASCADE, related_name="instances"
)
overrides = models.JSONField(default=dict, blank=True)
objects = FieldsetFieldManager()

class Meta:
unique_together = [["fieldset", "name"]]

def natural_key(self):
return self.name, self.fieldset.name

def get_field(self):
return self.field.get_field()
Loading

0 comments on commit 57ae51f

Please sign in to comment.