Using formsets in django-angular - angularjs

django-angular is very cool. In particular, I like how it automates rendering errors. I have a bunch of forms where this works very nicely. I am trying to do the same thing w/ formsets and running into problems.
First, there was an issue w/ the field names not mapping to the underlying angular models. I am using a nested model like this:
var parent = {
field1 : 'a',
field2: 'b',
child_model: [
{ childfield1: 'c',
childfield2: 'd'
},
{ childfield1: 'e',
childfield2: 'f'
}
]
}
In order to get this structure to map to the form fields, I use the following code:
class MyFormSet(BaseInlineFormSet):
def _construct_form(self, i, **kwargs):
"""
Instantiates and returns the i-th form instance in a formset.
"""
defaults = {
'auto_id': self.auto_id,
'prefix': self.add_prefix(i),
'error_class': self.error_class,
# THESE NEXT 3 LINES ARE THE DIFFERENT BIT...
'formset_class': self.__class__,
'scope_prefix': "%s[%d]" % (self.scope_prefix, i),
'form_name': "%s-%s" % (self.formset_name, i),
}
if self.is_bound:
defaults['data'] = self.data
defaults['files'] = self.files
if self.initial and 'initial' not in kwargs:
try:
defaults['initial'] = self.initial[i]
except IndexError:
pass
# Allow extra forms to be empty, unless they're part of
# the minimum forms.
if i >= self.initial_form_count() and i >= self.min_num:
defaults['empty_permitted'] = True
defaults.update(kwargs)
form = self.form(**defaults)
self.add_fields(form, i)
return form
class MyChildForm(Bootstrap3ModelForm, NgModelFormMixin, NgFormValidationMixin):
class Meta:
model = MyChildModel
def __init__(self, *args, **kwargs):
formset_class = kwargs.pop("formset_class", None)
super(MyChildForm, self).__init__(*args, **kwargs)
self.formset_class = formset_class
def get_widget_attrs(self, bound_field):
"""
just like the base class fn
except it sets "ng-model" using "get_qualified_model_field_name"
"""
attrs = super(NgModelFormMixin, self).get_widget_attrs(bound_field)
identifier = self.add_prefix(bound_field.name)
ng = {
'name': bound_field.name,
'identifier': identifier,
'model': self.get_qualified_model_field_name(bound_field.name)
}
if hasattr(self, 'Meta') and bound_field.name in getattr(self.Meta, 'ng_models', []):
attrs['ng-model'] = ng['model']
for key, fmtstr in self.ng_directives.items():
attrs[key] = fmtstr % ng
return attrs
def is_formset(self):
"""
tells whether or not the form is a member of a formset
"""
return self.formset_class is not None
def get_qualified_form_field_name(self, field_name):
"""
gets a field name suitable for ng use when binding to form
"""
if self.is_formset():
identifier = field_name
else:
identifier = self.add_prefix(field_name)
return format_html("{0}['{1}']", self.form_name, identifier)
def get_qualified_model_field_name(self, field_name):
"""
gets a field name suitable for ng use when binding to model
"""
if self.is_formset():
identifier = field_name
else:
identifier = self.add_prefix(field_name)
return format_html("{0}['{1}']", self.scope_prefix, identifier)
def MyChildModelFormSetFactory(*args, **kwargs):
instance = kwargs.pop("instance", None)
initial = kwargs.pop("initial", [])
queryset = kwargs.pop("queryset", MyChildModel.objects.none())
prefix = kwargs.pop("prefix", None)
scope_prefix = kwargs.pop("scope_prefix", None)
formset_name = kwargs.pop("formset_name", None)
kwargs.update({
"can_delete": False,
"extra": kwargs.pop("extra", 0),
"formset": MyFormSet,
"form": MyChildForm,
})
formset = inlineformset_factory(MyParentModel, MyChildModel, *args, **kwargs)
formset.scope_prefix = scope_prefix
formset.formset_name = formset_name
return formset(instance=instance, initial=initial, queryset=queryset)
The change in get_widget_attrs correctly renders fields like this:
<input class="form-control ng-pristine ng-untouched ng-valid ng-valid-parse ng-valid-required ng-valid-maxlength" id="id_my_formset_name-0.field_name" name="my_formset_name-0.field_name" ng-model="parent.child_model[0]['field_name']">
But when I try to render {{field.errors}} I just get a bunch of garbage. Instead of sensible elements w/ all the expected django-angular classes ("djng-field-errors", etc.), I get raw text like this:
(u'my_formset_name-0.my_scope_prefix-0.my_field_name', u'djng-form-control-feedback djng-field-errors', u'$dirty', u'$error.required', u'invalid', u'This field is required.')
Any ideas?
update:
For what it's worth, if I print out what .get_field_errors returns from a field from a "stand-alone" form I get:
<ul class="djng-form-control-feedback djng-field-errors" ng-show="my_form_name['field1'].$dirty" ng-cloak>
<li ng-show="my_form_name['field1'].$error.required" class="invalid">This field is required.</li>
</ul>
And if I print out what .get_field_errors returns from a field from a form that is part of a formset I get:
<ul class="errorlist">
<li>("my_formset_name-0['childfield1']", u'djng-form-control-feedback djng-field-errors', '$dirty', u'$error.required', 'invalid', u'This field is required.')</li>
</ul>
In addition to the HTML not being escaped, note that the parent element has the wrong class.

The problem was simple. django-angular forms use a custom error_class. But the _construct_form fn overrides that. I just needed to specify that same error class in the FormSet:
class MyFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
kwargs.update({
# makes sure that child forms render errors correctly...
"error_class": TupleErrorList,
})
super(MyFormSet, self).__init__(*args, **kwargs)

Related

Using Django formset displaying related names in template

I am trying to display the state values for each country name in Django app. To save the user response, I am using Django generic CreateView. My models look something like this:
class Question(model.Models):
ques_id = models.AutoField(primary_key=True)
country = models.ForeignKey(Country)
state = models.CharField(max_length=...)
class Test(model.Models):
test = models.AutoField(primary_key=True, )
test_num = models.CharField(max_length=6, )
class Response(model.Models):
response = models.AutoField(primary_key=True)
test_id = models.ForeignKey(Test, related_name='test', )
ques_offered = models.ForeignKey(Question, related_name='choice',
ans_submitted = models.CharField(max_length=240,
To display the available choices for field state for each country value (in the db), I am looping through Django management form for the formset in my template. However, I am unable to get to the values of field state instead I am getting the country values.
Additional info:
The views that I am using to achieve this:
class ResponseCreateView(CreateView):
template_name = ...
model = Test
form_class = # Form_name
def get_context_data(self, **kwargs):
data = super(ResponseCreateView, self).get_context_data(**kwargs)
if self.request.POST:
data['get_response'] = responseFormset(self.request.POST, self.request.FILES)
else:
data['get_response'] = responseFormset()
def form_valid(self, form):
context = self.get_context_data()
get_response = context['get_response']
with transaction.atomic():
if get_response.is_valid():
self.object = form.save()
get_response.instance = self.object
get_response.save()
return redirect('...')
else:
context.update({'get_response': get_response,
})
return self.render_to_response(context)
return super(ResponseCreateView, self).form_valid(form)

Testing Django Models leads to TypeError: 'str' object is not callable

I am currently testing this model:
models.py
class Tag(models.Model):
name = models.CharField(max_length=256)
language = models.CharField(max_length=256)
objects = models.Manager()
def __str__(self):
return self.name or ''
#property
def tags(self):
tags = self.tagging.values('tag')
return tags.values('tag_id', 'tag__name', 'tag__language')
With these tests:
test_models.py
class TagTests(TestCase):
def setUp(self):
self.tag_name = "name of the tag"
self.tag_language = "language of tag"
self.tag = Tag.objects.create(name=self.tag_name, language=self.tag_language)
def test_name_label(self):
tag = Tag.objects.get(name=self.tag_name)
field_label = tag._meta.get_field('name').verbose_name()
self.assertEqual(field_label, 'name')
def test_tag_size(self):
tag = Tag.objects.get(name=self.tag_name)
max_length = tag._meta.get_field('name').max_length
self.assertEqual(max_length, 256)
def test_str(self):
"""Test for string representation"""
self.assertEqual(str(self.tag), self.tag.name)
I currently get the following error for the method test_name_label(self):
FAILED frontend/tests/test_models.py::TagTests::test_name_label - TypeError: 'str' object is not callable
How can I get rid of it? What am I doing wrong here?
Field.verbose_name is a string, not a method. Just remove parenthesis from it:
field_label = tag._meta.get_field('name').verbose_name

Django-3.1/DRF/React: Unable to save nested images (linked through GenericRelation)

I am building a Django+DRF/React app (simple blog app) and i am facing difficulties saving nested images
Model Structure
Model:
Post
Children:
details: ContentType Model ( DRF: save is successfull )
images: ContentType Model ( DRF : save is not successfull )
Process
Send images from <input type="file" multiple />
Process data through FormData
Catch request.data and process it
class PostFormView(generics.RetrieveUpdateDestroyAPIView):
queryset = Post._objects.is_active()
serializer_class = PostModelSerializer
permission_classes = (IsOwnerOr401,)
parser_classes = (parsers.MultiPartParser,parsers.JSONParser,
parsers.FormParser, parsers.FileUploadParser)
lookup_field = 'slug'
lookup_url_kwarg = 'slug'
def get_queryset(self):
return super().get_queryset().annotate(**sharedAnnotations(request=self.request))
def update(self, request, *args, **kwargs):
data = request.data
_images = data.getlist('images')
images = []
for _ in _images:
if isinstance(_, dict):
images.append(images)
continue
images.append({'image': _, 'object_id': self.get_object().pk, 'content_type': self.get_object().get_content_type().pk})
data['images'] = images
print(data)
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
instance._prefetched_objects_cache = {}
return Response(serializer.data)
Save images (FAIL):
class MediaModelSerializer(ContentTypeModelSerializer):
# inherits object_id & content_type fields just to avoid writing them over and over alongside (create & update fns)
class Meta:
model = Media
fields='__all__'
class PostModelSerializer(WritableNestedModelSerializer):
is_active = serializers.BooleanField(default=True)
path = serializers.HyperlinkedIdentityField(
view_name="api:post-detail", lookup_field='slug')
images = MediaModelSerializer(many=True)
details = DetailModelSerializer(required=False, many=True)
# annotated fields
is_author = serializers.BooleanField(read_only=True, default=False)
class Meta:
model = Post
fields = '__all__'
read_only_fields = ['is_locked', 'slug', 'user', 'is_author']
def create(self, validated_data):
return super().create(validated_data)
def update(self, instance, validated_data):
return super().update(instance, validated_data)
The print(data) statement from PostFormView.update(self, request, *args, **kwargs) (after manipulation) returns this:
<QueryDict: {'id': ['8'], ..., 'images': [[{'image': <InMemoryUploadedFile: bmw_3.jpeg (image/jpeg)>, 'object_id': 8, 'content_type': 20}, {'image': <InMemoryUploadedFile: bmw_2.jpeg (image/jpeg)>, 'object_id': 8, 'content_type': 20}, {'image': <InMemoryUploadedFile: bmw_1.jpeg (image/jpeg)>, 'object_id': 8, 'content_type': 20}]]}>
Server returns 400_BAD_REQUEST because images were not passed to PostModelSerializer
{"images":["This field is required."]}
i've been facing this issue for 3 days and i can't wrap my head around the root cause.
Thank you for your help.
i have been looking all over the internet but i could not find any anwsers so i had to go this way
I have removed the processing part from PostFormView.update(...) and accessed the images directly in the create & update methods of the ModelSerializer. I'll figure out later on how to handle removing these images
Here's the code:
class PostModelSerializer(WritableNestedModelSerializer):
is_active = serializers.BooleanField(default=True)
path = serializers.HyperlinkedIdentityField(
view_name="api:post-detail", lookup_field='slug')
images = MediaModelSerializer(read_only=True, many=True)
details = DetailModelSerializer(required=False, many=True)
# annotated fields
is_author = serializers.BooleanField(read_only=True, default=False)
class Meta:
model = Post
fields = '__all__'
read_only_fields = ['is_locked', 'slug', 'user', 'is_author']
def create(self, validated_data):
instance = super().create(validated_data)
request = self.context.get('request', None)
if request:
try:
images = request.data.getlist('images')
for image in images:
self.instance.images.create(image=image)
except Exception as e:
pass
return instance
def update(self, instance, validated_data):
instance = super().update(instance, validated_data)
request = self.context.get('request', None)
if request:
try:
images = request.data.getlist('images')
for image in images:
self.instance.images.create(image=image)
except Exception as e:
pass
return instance
If anyone has faced this issue before and managed to resolve it, please post your answer below.
Thank you !

Custom field panel performance in stream block

I'm using wagtailgmaps for add google maps feature to my project. Use it
is easy, and i want add a stream block for use Google Maps block to the
content.
But, we have a big problem, this package only add a new edit_handler, it doesn't add a new model or field, only add a new handler for the admin.
So, my question is, how can i create a Google Maps stream block adding
the handler feature?
Thanks!
For create a new StreamBlock do you need create a widget for your field:
def render_form(self, value, prefix='', errors=None):
widget = self.field.widget
widget_attrs = {'id': prefix, 'placeholder': self.label}
field_value = self.value_for_form(value)
if hasattr(widget, 'render_with_errors'):
widget_html = widget.render_with_errors(prefix, field_value, attrs=widget_attrs, errors=errors)
widget_has_rendered_errors = True
else:
widget_html = widget.render(prefix, field_value, attrs=widget_attrs)
widget_has_rendered_errors = False
return render_to_string('wagtailadmin/block_forms/field.html', {
'name': self.name,
'classes': self.meta.classname,
'widget': widget_html,
'field': self.field,
'errors': errors if (not widget_has_rendered_errors) else None
})
Normally, all blocks in the base core have this function, and you can see that use widget.
So, first we have to create a widget for the map:
class MapsField(HiddenInput):
***
def __init__(self, *args, **kwargs):
self.address_field = kwargs.pop('address_field', self.address_field)
***
super(MapField, self).__init__(*args, **kwargs)
class Media:
css = {
'all': ('*/**/geo-field.css',)
}
js = (
'*/**/geo-field.js',
'https://maps.google.com/maps/api/js?key={}&libraries=places'
.format(
GOOGLE_MAPS_V3_APIKEY
),
)
def render(self, name, value, attrs=None):
out = super(MapField, self).render(name, value, attrs)
location = format_html(
'<div class="input">'
'<input id="_id_{}_latlng" class="geo-field-location" maxlength="250" type="text">' # NOQA
'</div>',
name
)
***
return mark_safe(
'<script>window["{}"] = {};</script>'.format(data_id, json_data) +
out +
location +
'<div class="geo-field" data-data-id="{}"></div>'.format(data_id) +
"""
<script>
(function(){
if (document.readyState === 'complete') {
return initializeGeoFields();
}
$(window).load(function() {
initializeGeoFields();
});
})();
</script>
"""
)
Now, we will create a new block extending the default wagtail block:from
from wagtail.wagtailcore.blocks import FieldBlock
from wagtailgeowidget.widgets import GeoField
def __init__(self, address_field=None, required=True, help_text=None,
**kwargs):
self.field_options = {}
self.address_field = address_field
super(GeoBlock, self).__init__(**kwargs)
#cached_property
def field(self):
field_kwargs = {'widget': GeoField(
srid=4326,
id_prefix='',
address_field=self.address_field,
)}
field_kwargs.update(self.field_options)
return forms.CharField(**field_kwargs)
def clean(self, value):
if not value:
value = "SRID={};POINT({} {})".format(
4326,
GEO_WIDGET_DEFAULT_LOCATION['lng'],
GEO_WIDGET_DEFAULT_LOCATION['lat']
)
return super(GeoBlock, self).clean(value)
def render_form(self, value, prefix='', errors=None):
if value and isinstance(value, dict):
value = "SRID={};POINT({} {})".format(value['srid'],
value['lng'],
value['lat'])
return super(GeoBlock, self).render_form(value, prefix, errors)
def to_python(self, value):
if isinstance(value, dict):
return value
value = geosgeometry_str_to_struct(value)
value = {
'lat': value['y'],
'lng': value['x'],
'srid': value['srid'],
}
return super(MapBlock, self).to_python(value)
This is basically the structure of wagtail block.

Function does't add data to model(via serializer)

Here is function I wrote, it checks field called 'url' inside 'Url1' Model and continues IF it's empty.
def mozs():
getids = Url1.objects.values_list('id', flat=True)
for id in getids:
if Url1.objects.get(id=id).pda == None:
authorities= {"pda": 58.26193857945012, "upa": 36.56733779379807}
authorities['keyword'] = id
serializer = MozSerializer(data=authorities)
if serializer.is_valid():
serializer.save()
print "For %d we added %s" % (id, authorities)
Here is output:
For 37 we added {'keyword': 37, 'pda': 58.26193857945012, 'upa': 36.56733779379807}
But it doesn't add it. Here is serializer:
class MozSerializer(serializers.Serializer):
keyword = serializers.PrimaryKeyRelatedField(queryset=KW.objects.all())
pda = serializers.FloatField()
upa = serializers.FloatField()
def save(self):
keyword = self.validated_data['keyword']
pda = self.validated_data['pda']
upa = self.validated_data['upa']
Url1.objects.update(pda=pda, upa=upa)

Resources