Skip to content

Commit 21ad01e

Browse files
timgrahamWaVEV
authored andcommitted
wip forms
1 parent e66fcf2 commit 21ad01e

File tree

6 files changed

+568
-2
lines changed

6 files changed

+568
-2
lines changed

django_mongodb_backend/fields/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
from .embedded_model import EmbeddedModelField
55
from .embedded_model_array import EmbeddedModelArrayField
66
from .json import register_json_field
7+
from .multiple_embedded_model import MultipleEmbeddedModelField
78
from .objectid import ObjectIdField
89

910
__all__ = [
1011
"register_fields",
1112
"ArrayField",
1213
"EmbeddedModelArrayField",
1314
"EmbeddedModelField",
15+
"MultipleEmbeddedModelField",
1416
"ObjectIdAutoField",
1517
"ObjectIdField",
1618
]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from ..forms import MultipleEmbeddedModelFormField
2+
from . import EmbeddedModelField
3+
from .array import ArrayField
4+
5+
6+
class MultipleEmbeddedModelField(ArrayField):
7+
def __init__(self, model, **kwargs):
8+
super().__init__(EmbeddedModelField(model), **kwargs)
9+
10+
def deconstruct(self):
11+
name, path, args, kwargs = super().deconstruct()
12+
if (
13+
path
14+
== "django_mongodb_backend.fields.multiple_embedded_model.MultipleEmbeddedModelField"
15+
):
16+
path = "django_mongodb_backend.fields.MultipleEmbeddedModelField"
17+
kwargs.update(
18+
{
19+
"model": self.base_field.embedded_model,
20+
"size": self.size,
21+
}
22+
)
23+
del kwargs["base_field"]
24+
return name, path, args, kwargs
25+
26+
def formfield(self, **kwargs):
27+
return super().formfield(
28+
**{
29+
"form_class": MultipleEmbeddedModelFormField,
30+
"model": self.base_field.embedded_model,
31+
"max_length": self.size,
32+
"prefix": self.name,
33+
**kwargs,
34+
}
35+
)

django_mongodb_backend/forms/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .fields import (
22
EmbeddedModelArrayField,
33
EmbeddedModelField,
4+
MultipleEmbeddedModelFormField,
45
ObjectIdField,
56
SimpleArrayField,
67
SplitArrayField,
@@ -10,6 +11,7 @@
1011
__all__ = [
1112
"EmbeddedModelArrayField",
1213
"EmbeddedModelField",
14+
"MultipleEmbeddedModelFormField",
1315
"SimpleArrayField",
1416
"SplitArrayField",
1517
"SplitArrayWidget",
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from django import forms
2+
from django.core.exceptions import ValidationError
3+
from django.db.models import Model
4+
from django.forms import formset_factory, model_to_dict
5+
from django.forms.models import modelform_factory
6+
from django.utils.html import format_html, format_html_join
7+
from django.utils.translation import gettext_lazy as _
8+
9+
10+
class MultipleEmbeddedModelFormField(forms.Field):
11+
default_error_messages = {"incomplete": _("Enter all required values.")}
12+
13+
def __init__(self, model, prefix, max_length=None, *args, **kwargs):
14+
kwargs.pop("base_field")
15+
self.model = model
16+
self.prefix = prefix
17+
self.model_form_cls = modelform_factory(model, fields="__all__")
18+
self.formset = formset_factory(
19+
form=self.model_form_cls, can_delete=True, max_num=max_length
20+
)
21+
kwargs["widget"] = MultipleEmbeddedModelWidget(self.model_form_cls.__name__)
22+
super().__init__(*args, **kwargs)
23+
24+
def clean(self, value):
25+
if not value:
26+
return []
27+
formset = self.formset(value, prefix=self.prefix)
28+
if not formset.is_valid():
29+
raise ValidationError(formset.errors + formset.non_form_errors())
30+
cleaned_data = []
31+
for data in formset.cleaned_data:
32+
if data.get("DELETE", True):
33+
continue
34+
data.pop("DELETE")
35+
cleaned_data.append(self.model_form_cls._meta.model(**data))
36+
return cleaned_data
37+
38+
def has_changed(self, initial, data):
39+
formset_initial = []
40+
for initial_data in initial or []:
41+
formset_initial.append(forms.model_to_dict(initial_data))
42+
formset = self.formset(data, initial=formset_initial, prefix=self.prefix)
43+
return formset.has_changed()
44+
45+
def get_bound_field(self, form, field_name):
46+
return MultipleEmbeddedModelBoundField(form, self, field_name)
47+
48+
49+
class MultipleEmbeddedModelBoundField(forms.BoundField):
50+
def __init__(self, form, field, name):
51+
super().__init__(form, field, name)
52+
data = self.data if form.is_bound else None
53+
formset_initial = []
54+
if self.initial is not None:
55+
for initial in self.initial:
56+
if isinstance(initial, Model):
57+
formset_initial.append(model_to_dict(initial))
58+
self.formset = field.formset(data, initial=formset_initial, prefix=self.html_name)
59+
60+
def __getitem__(self, idx):
61+
if not isinstance(idx, (int | slice)):
62+
raise TypeError
63+
return self.formset[idx]
64+
65+
def __iter__(self):
66+
yield from self.formset
67+
68+
def __str__(self):
69+
table = format_html_join(
70+
"\n", "<tbody>{}</tbody>", ((form.as_table(),) for form in self.formset)
71+
)
72+
table = format_html("\n<table>" "\n{}" "\n</table>", table)
73+
return format_html("{}\n{}", table, self.formset.management_form)
74+
75+
def __len__(self):
76+
return len(self.formset)
77+
78+
79+
class MultipleEmbeddedModelWidget(forms.Widget):
80+
def __init__(self, field_id, attrs=None):
81+
self.field_id = field_id
82+
super().__init__(attrs)
83+
84+
def render(self, name, value, attrs=None, renderer=None):
85+
raise NotImplementedError("This widget is not meant to be rendered.")
86+
87+
def id_for_label(self, id_):
88+
return f"{id_}-0-{self.field_id}"
89+
90+
def value_from_datadict(self, data, files, name):
91+
return {key: data[key] for key in data if key.startswith(name)}
92+
93+
def value_omitted_from_data(self, data, files, name):
94+
return any(key.startswith(name) for key in data)

tests/model_forms_/models.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from django.db import models
22

3+
<<<<<<< HEAD
34
from django_mongodb_backend.fields import EmbeddedModelArrayField, EmbeddedModelField
5+
=======
6+
from django_mongodb_backend.fields import EmbeddedModelField, MultipleEmbeddedModelField
7+
>>>>>>> fc07a29 (wip forms)
48
from django_mongodb_backend.models import EmbeddedModel
59

610

@@ -39,8 +43,7 @@ def __str__(self):
3943

4044
class Movie(models.Model):
4145
title = models.CharField(max_length=255)
42-
reviews = EmbeddedModelArrayField(Review)
43-
featured_reviews = EmbeddedModelArrayField(Review, null=True, blank=True, max_size=2)
46+
reviews = MultipleEmbeddedModelField(Review, null=True)
4447

4548
def __str__(self):
4649
return self.title

0 commit comments

Comments
 (0)