Skip to content

Commit

Permalink
ad ability do inspect django mddels
Browse files Browse the repository at this point in the history
  • Loading branch information
saxix committed Jul 8, 2024
1 parent 9cf48dc commit 1e8834e
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 15 deletions.
90 changes: 89 additions & 1 deletion src/hope_flex_fields/admin/fieldset.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,79 @@
from django import forms
from django.contrib import messages
from django.contrib.admin import ModelAdmin, register
from django.contrib.contenttypes.models import ContentType
from django.db.transaction import atomic
from django.forms import modelform_factory
from django.shortcuts import render

from admin_extra_buttons.decorators import button
from admin_extra_buttons.mixins import ExtraButtonsMixin

from ..forms import FieldsetForm
from ..models import Fieldset
from ..models import FieldDefinition, Fieldset, FlexField
from ..utils import get_kwargs_from_formfield
from .flexfield import FieldsetFieldTabularInline


class FieldSetForm(forms.Form):
content_type = forms.ModelChoiceField(queryset=ContentType.objects.all())

def analyse(self):
ct: ContentType = self.cleaned_data["content_type"]
model_class = ct.model_class()
model_form = modelform_factory(
model_class, exclude=(model_class._meta.pk.name,)
)
errors = []
fields = []
config = {}
for name, field in model_form().fields.items():
try:
fd = FieldDefinition.objects.get(name=type(field).__name__)
fld = FlexField(
name=name, field=fd, attrs=get_kwargs_from_formfield(field)
)
fld.attrs = fld.get_merged_attrs()
fields.append(fld)
config["name"] = {"definition": fd.name, "attrs": fld.attrs}
fld.get_field()
except FieldDefinition.DoesNotExist:
errors.append(
{
"name": name,
"error": f"Field definition for '{type(field).__name__}' does not exist",
}
)
return {
"fields": fields,
"errors": errors,
"config": config,
"content_type": ct,
}


class FieldSetForm2(forms.Form):
content_type = forms.ModelChoiceField(
queryset=ContentType.objects.all(), widget=forms.HiddenInput
)
config = forms.JSONField(widget=forms.HiddenInput)

def save(self):
with atomic():
ct: ContentType = self.cleaned_data["content_type"]
model_class = ct.model_class()
fs, __ = Fieldset.objects.get_or_create(
name=f"{model_class._meta.app_label}_{model_class._meta.model_name}"
)
for name, info in self.cleaned_data["config"].items():
fd = FieldDefinition.objects.get(name=info["definition"])
fs.fields.get_or_create(name=name, field=fd, attrs=info["attrs"])


class inspect_field:
pass


@register(Fieldset)
class FieldsetAdmin(ExtraButtonsMixin, ModelAdmin):
list_select_related = True
Expand All @@ -18,6 +82,30 @@ class FieldsetAdmin(ExtraButtonsMixin, ModelAdmin):
inlines = [FieldsetFieldTabularInline]
form = FieldsetForm

@button()
def create_from_content_type(self, request):
ctx = self.get_common_context(request, title="Create from ContentType")
if request.method == "POST":
if "config" in request.POST:
form = FieldSetForm2(request.POST, request.FILES)
form.is_valid()
form.save()
else:
form = FieldSetForm(request.POST, request.FILES)
if form.is_valid():
result = form.analyse()
ctx.update(**result)
form = FieldSetForm2(
initial={
"content_type": result["content_type"],
"config": result["config"],
}
)
else:
form = FieldSetForm()
ctx["form"] = form
return render(request, "flex_fields/fieldset/analyse.html", ctx)

@button()
def inspect(self, request, pk):
ctx = self.get_common_context(request, pk, title="Inspect")
Expand Down
10 changes: 1 addition & 9 deletions src/hope_flex_fields/apps.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
from django.apps import AppConfig
from django.db.models.signals import post_migrate

from strategy_field.utils import fqn


def create_default_fields(sender, **kwargs):
from hope_flex_fields.models import FieldDefinition
from hope_flex_fields.models.base import get_default_attrs
from hope_flex_fields.registry import field_registry
from hope_flex_fields.utils import get_kwargs_for_field

for fld in field_registry:
FieldDefinition.objects.get_or_create(
name=fld.__name__,
field_type=fqn(fld),
defaults={"attrs": get_kwargs_for_field(fld, get_default_attrs())},
)
FieldDefinition.objects.get_from_django_field(fld)


class Config(AppConfig):
Expand Down
21 changes: 19 additions & 2 deletions src/hope_flex_fields/models/definition.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import logging
from inspect import isclass

from django import forms
from django.db import models
from django.db.models import UniqueConstraint
from django.utils.translation import gettext as _

from strategy_field.fields import StrategyClassField
from strategy_field.utils import fqn

from hope_flex_fields.utils import get_kwargs_for_field
from hope_flex_fields.utils import get_kwargs_from_field_class

from ..fields import FlexFormMixin
from ..registry import field_registry
Expand All @@ -20,6 +23,20 @@ class FieldDefinitionManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)

def get_from_django_field(self, django_field: "forms.Field|type[forms.Field]"):
if isinstance(django_field, forms.Field):
fld = type(django_field)
elif isclass(django_field) and issubclass(django_field, forms.Field):
fld = django_field
else:
raise ValueError(django_field)
name = fld.__name__
return FieldDefinition.objects.get_or_create(
name=name,
field_type=fqn(fld),
defaults={"attrs": get_kwargs_from_field_class(fld, get_default_attrs())},
)[0]


class FieldDefinition(AbstractField):
field_type = StrategyClassField(registry=field_registry)
Expand Down Expand Up @@ -49,7 +66,7 @@ def set_default_arguments(self):
if not isinstance(self.attrs, dict) or not self.attrs:
self.attrs = get_default_attrs()
if self.field_type:
attrs = get_kwargs_for_field(self.field_type)
attrs = get_kwargs_from_field_class(self.field_type)
attrs.update(**self.attrs)
self.attrs = attrs

Expand Down
44 changes: 44 additions & 0 deletions src/hope_flex_fields/templates/flex_fields/fieldset/analyse.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{% extends "admin_extra_buttons/action_page.html" %}
{% block action-content %}
{{ original }}
{% if fields %}
<form method="post" id="create-form">
{% csrf_token %}
{{ form }}
<input type="submit" name="create" value="Create">
</form>
{% if errors %}
<h3>Errors</h3>
<table>
{% for err in errors %}
<tr>
<td>{{ err.name }}</td>
<td>{{ err.error }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
<div>
<h3>Fields</h3>
<table>
{% for fld in fields %}
<tr>
<td>{{ fld }}</td>
<td>{{ fld.field }}</td>
<td>{{ fld.attrs }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<form method="post" id="analyse-form">
<table>
{% csrf_token %}
{{ form }}
</table>
<input type="submit" value="Analyse">
</form>
<div>
{% endif %}
</div>

{% endblock %}
28 changes: 27 additions & 1 deletion src/hope_flex_fields/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import inspect

from django import forms
from django.forms.fields import DateTimeFormatsIterator
from django.utils.text import slugify


def namefy(value):
return slugify(value).replace("-", "_")


def get_kwargs_for_field(field, extra: dict | None = None):
def get_kwargs_from_field_class(field, extra: dict | None = None):
sig: inspect.Signature = inspect.signature(field)
arguments = extra or {}
field_arguments = {
Expand All @@ -17,3 +19,27 @@ def get_kwargs_for_field(field, extra: dict | None = None):
}
arguments.update(field_arguments)
return arguments


def get_kwargs_from_formfield(field: forms.Field):
from hope_flex_fields.models import FieldDefinition

fd = FieldDefinition.objects.get(name=type(field).__name__)
ret = {}
for attr_name in fd.attrs.keys():
if attr_name in (
"widget",
"validators",
"error_messages",
"error_messages",
"help_text",
"label",
):
continue
value = getattr(field, attr_name)
# if attr_name in ("help_text", "label"):
# value = str(value)
if isinstance(value, DateTimeFormatsIterator):
value = [str(v) for v in value]
ret[attr_name] = value
return ret
1 change: 1 addition & 0 deletions tests/.coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ exclude_lines =
if DEBUG:
except NoReverseMatch:
if TYPE_CHECKING:
raise ValueError(...)

ignore_errors = True

Expand Down
27 changes: 27 additions & 0 deletions tests/admin/test_admin_fieldset.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django import forms
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse

import pytest
Expand All @@ -8,6 +10,8 @@
FlexFieldFactory,
)

from hope_flex_fields.models import Fieldset

pytestmark = [pytest.mark.admin, pytest.mark.smoke, pytest.mark.django_db]


Expand All @@ -33,3 +37,26 @@ def test_fieldset_test(app, record):
res = res.forms["test"].submit()
messages = [s.message for s in res.context["messages"]]
assert messages == ["Valid"]


@pytest.mark.parametrize(
"model_class",
[
User,
],
)
def test_fieldset_create_from_content_type(app, model_class):
url = reverse("admin:hope_flex_fields_fieldset_create_from_content_type")
res = app.get(url)
res = res.forms["analyse-form"].submit()
assert res.status_code == 200
res.forms["analyse-form"]["content_type"] = ContentType.objects.get_for_model(
model_class
).pk
res = res.forms["analyse-form"].submit()
res.forms["create-form"].submit()
fs = Fieldset.objects.filter(
name=f"{model_class._meta.app_label}_{model_class._meta.model_name}"
).first()
assert fs
assert fs.fields.exists()
4 changes: 2 additions & 2 deletions tests/extra/testutils/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ class Meta:
@classmethod
def _create(cls, model_class, *args, **kwargs):
if "attrs" in kwargs:
from hope_flex_fields.utils import get_kwargs_for_field
from hope_flex_fields.utils import get_kwargs_from_field_class

attrs = get_kwargs_for_field(kwargs["field_type"])
attrs = get_kwargs_from_field_class(kwargs["field_type"])
attrs.update(**kwargs["attrs"])
kwargs["attrs"] = attrs
return super()._create(model_class, *args, **kwargs)
Expand Down
7 changes: 7 additions & 0 deletions tests/models/test_model_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,10 @@ def test_override(db):
with pytest.raises(ValidationError) as e:
field.clean(11)
assert e.value.messages == [r"Invalid format. Allowed Regex is '\d$'"]


@pytest.mark.parametrize("form_field", [forms.CharField(), forms.CharField])
def test_get_from_django_field(db, form_field):
from hope_flex_fields.models import FieldDefinition

assert FieldDefinition.objects.get_from_django_field(form_field)

0 comments on commit 1e8834e

Please sign in to comment.