Custom field panel performance in stream block - wagtail

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.

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)

How do i call a primary key from another view in another form in django?

I have a stocks model that relies on GRN (Goods Receiving Note) model primary key in order to store stocks of a certain GRN in the database. I would like to humbly ask for guidance on how i can click a GRN and enter stocks in a form with the open GRN No already selected in this form.
Below are my views:
def grns(request):
grns = GRN.objects.all()
return render (request, 'products/GRNS.html', {'grns' : grns})
def grn(request, pk):
gnrObj = GRN.objects.get(GRN_id = pk)
return render(request, 'products/stocks_in.html', {'grn': gnrObj})
def createGrn(request):
form = GrnForm()
if request.method == 'POST':
form = GrnForm(request.POST)
if form.is_valid():
form.save()
return redirect('grns')
context = {'form': form}
return render (request, 'products/grn_form.html', context)
def stocks(request):
stocks = Stock.objects.all()
return render (request, 'products/stocks_in.html', {'stocks' : stocks})
def stock(request, pk):
stockObj = stock.objects.get(GRN_id = pk)
return render(request, 'products/stocks_in_form.html', {'stock': stockObj})
def createStock(request):
form = StockForm()
if request.method == 'POST':
form = StockForm(request.POST)
if form.is_valid():
form.save()
return redirect('stocks')
context = {'form': form}
return render (request, 'products/stocks_in_form.html', context)
Below are my urls:
#GRNS Urls
path('grns', views.grns, name = "grns"),
path('create-grn/', views.createGrn, name = "create-grn"),
path('grn/<str:pk>/', views.grn, name = "grn"),
#Stocks Urls
path('stocks', views.stocks, name = "stocks"),
path('create-stock/', views.createStock, name = "create-stock"),
path('stock/<str:pk>/', views.stock, name = "stock"),
Below are my forms:
class GrnForm(ModelForm):
class Meta:
model = GRN
fields = ['GRN_no', 'date', 'supplier']
def __init__(self, *args, **kwargs):
super(GrnForm, self).__init__(*args, **kwargs)
self.fields['GRN_no'].widget.attrs.update(
{'class': 'form-control'}
)
self.fields['date'].widget.attrs.update(
{'class': 'form-control', 'type': 'date'}
)
self.fields['supplier'].widget.attrs.update(
{'class': 'form-control'}
)
class StockForm(ModelForm):
class Meta:
model = Stock
fields = ['GRN', 'product', 'warehouse', 'unit_price', 'quantity']
def __init__(self, *args, **kwargs):
super(StockForm, self).__init__(*args, **kwargs)
self.fields['GRN'].widget.attrs.update(
{'class': 'form-control', 'value':'grn.GRN_id'}
)
self.fields['product'].widget.attrs.update(
{'class': 'form-control'}
)
self.fields['warehouse'].widget.attrs.update(
{'class': 'form-control'}
)
self.fields['unit_price'].widget.attrs.update(
{'class': 'form-control'}
)
self.fields['quantity'].widget.attrs.update(
{'class': 'form-control'}
)

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 !

Wagtail Snippets permissions per group

I have a Wagtail site where every group can work on a different page tree, with different images and documents permissions.
That is a multisite setup where I am trying to keep sites really separate.
Is that possible to limit the snippets permissions on a per-group basis?
I would like my groups to see just a subset of the snippets.
I was facing something similar when I wanted to use Site settings.
The only solution I found was to create a custom model and using ModelAdmin.
Some ‘snippets’ to get you on the run:
class SiteSettings(models.Model):
base_form_class = SiteSettingsForm
COMPANY_FORM_CHOICES = (
('BED', 'Bedrijf'),
('ORG', 'Organisatie'),
('STI', 'Stichting'),
('VER', 'Vereniging'),
)
site = models.OneToOneField(
Site,
unique = True,
db_index = True,
on_delete = models.CASCADE,
verbose_name = _('site'),
related_name = 'site_settings',
help_text = _('The sites these setting belong to.')
)
company_name = models.CharField(
_('company name'),
blank = True,
max_length = 50,
help_text = _('De naam van het bedrijf of de organisatie.')
)
company_form = models.CharField(
_('company form'),
max_length = 3,
blank = True,
default = 'COM',
choices = COMPANY_FORM_CHOICES
)
...
class MyPermissionHelper(PermissionHelper):
def user_can_edit_obj(self, user, obj):
result = super().user_can_edit_obj(user, obj)
if not user.is_superuser:
user_site = get_user_site(user)
result = user_site and user_site == obj.site
return result
class SiteSettingsAdmin(ThumbnailMixin, ModelAdmin):
model = SiteSettings
menu_label = _('Site settings')
menu_icon = 'folder-open-inverse'
add_to_settings_menu = True
list_display = ['admin_thumb', 'company_name', 'get_categories']
list_select_related = True
list_display_add_buttons = 'site'
thumb_image_field_name = 'logo'
thumb_col_header_text = _('logo')
permission_helper_class = MyPermissionHelper
create_view_class = CreateSiteSettingsView
...
class CreateSiteSettingsView(SiteSettingsViewMixin, CreateView):
#cached_property
def sites_without_settings(self):
sites = get_sites_without_settings()
if not sites:
messages.info(
self.request,
_('No sites without settings found.')
)
return sites
def dispatch(self, request, *args, **kwargs):
if request.user.is_superuser and not self.sites_without_settings:
return redirect(self.url_helper.get_action_url('index'))
return super().dispatch(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial().copy()
current_site = self.request.site
initial.update({
'company_name': current_site.site_name}
)
if self.request.user.is_superuser:
initial.update({
'site': current_site}
)
return initial
def get_form(self):
form = super().get_form()
flds = form.fields
if self.request.user.is_superuser:
fld = form.fields['site']
fld.queryset = self.sites_without_settings.order_by(
Lower('site_name')
)
return form
def form_valid(self, form):
instance = form.save(commit=False)
if not self.request.user.is_superuser:
instance.site = self.request.site
instance.save()
messages.success(
self.request, self.get_success_message(instance),
buttons=self.get_success_message_buttons(instance)
)
return redirect(self.get_success_url())

Using formsets in django-angular

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)

Resources