Wagtail - how to access StructBlock class attribute inside block - django

I'm trying to override my block template as described here:
https://github.com/wagtail/wagtail/issues/3857
I added a BooleanBlock inside the class and tried to use that value to change the template but I get an error "no attribute found".
class Features_Block(StructBlock):
title = CharBlock()
description = TextBlock(required=False)
use_other_template = BooleanBlock(default=False, required=False)
class Meta:
icon = 'list-ul'
def get_template(self, context=None):
if self.use_other_template:
return 'other_template.html'
return 'original_template.html'
I have found this thread which might be the answer but I don't understand how to implement it for my case:
https://github.com/wagtail/wagtail/issues/4387

The get_template method doesn't receive the block's value as a parameter, so there's no reliable way to vary the chosen template according to that value. You might be able to dig around in the calling template's context to retrieve the block value, as in Matt's answer, but this means the internals of Features_Block will be tied to the use of particular variable names in the calling template, which is a bad thing for reusable code.
(Accessing self.use_other_template doesn't work because self, the Features_Block object, doesn't hold on to the block value as a property of itself - it only serves as a translator between different representations. So, it knows how to render a given dictionary of data as HTML, but that dictionary of data is not something that 'belongs' to the Features_Block.)
get_template is called from the block's render method, which does receive the block value, so overriding render will allow you to vary the template based on the block value:
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
class Features_Block(StructBlock):
# ...
def render(self, value, context=None):
if value['use_other_template']:
template = 'other_template.html'
else:
template = 'original_template.html'
if context is None:
new_context = self.get_context(value)
else:
new_context = self.get_context(value, parent_context=dict(context))
return mark_safe(render_to_string(template, new_context))

Check the context that's passed to get_template.
class Features_Block(StructBlock):
title = CharBlock()
description = TextBlock(required=False)
use_other_template = BooleanBlock(default=False, required=False)
class Meta:
icon = 'list-ul'
def get_template(self, context=None):
if context and context['block'].value['use_other_template']:
return 'other_template.html'
return 'original_template.html'

Related

Can't show new added attr in template from object in Queryset in UpdateView (Django)

I am trying to output a list of additional model objects that are affected by this main object. The main model doesn't affect the additional one, so I'm trying to add the list to the new object attribute. When I print .__dict__ of an object after adding a new attribute, everything is OK, but the template outputs a blank.. How do I fix this?
My code in UpdateView:
def get_queryset(self):
print('ID: ', self.kwargs['adapter_account_id'])
deals = Deal.objects.filter(adapter_account=self.kwargs['adapter_account_id'])
adapter_acc = AdapterAccount.objects.filter(id=self.kwargs['adapter_account_id'])
for obj in adapter_acc:
obj.deals = deals
print(adapter_acc[0].__dict__)
return adapter_acc
Output for .__dict__:
{'_state': <django.db.models.base.ModelState object at 0x7fa0590547c0>, 'id': 13,
'adapter_id': 9,
'params': {'secret': 'secret', 'pay_gate': 'VV', 'product_id': '10731', 'merchant_id': '1998'},
'deals':<QuerySet [<Deal:#28>, <Deal:#30>]>}
My try to render value in template:
{{ adapteraccount.deals}}
For example, {{ adapteraccount.params}} works fine.
UPD: obj.save() in loop doesn't have any effect.
QuerySet.update(deals=deals) doesn't working because the model does not contain this field.
I tried adding an attribute that is not included in the model even a normal string and nothing worked. So I decided to go the other way and modified render_to_response:
def render_to_response(self, context, **response_kwargs):
"""
Return a response, using the `response_class` for this view, with a
template rendered with the given context.
Pass response_kwargs to the constructor of the response class.
"""
deals = Deal.objects.filter(adapter_account=self.kwargs['adapter_account_id'])
context['deals'] = deals
response_kwargs.setdefault('content_type', self.content_type)
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
using=self.template_engine,
**response_kwargs
)

Passing a variable defined in previous form to another form

So I have this flask app I'm making and I need some help with a variable access.
Most of the time, when you define a form in flask, you'll do the following :
class MyForm(Form):
my_field = StringField('I'm a field')
my_submit = SubmitField('Go!')
And when the time comes where you need the form, you'll declare an instance of that class with form = MyForm()
Up to here, it's all good, However :
If you want say, a SelectField (Dropdown) where the choices depend on the answers of a previous form, you need to be able to give the new form those choices. This is what I'm trying to achieve, but I can't get a variable to keep its contents.
Here is my Form code (Above the page code):
class DataMappingForm(Form):
dm_choices = #I need this array !
DMpatient_id = SelectField(u'Select Patient ID Column',
choices=dm_choices, validators=[Required()])
...
Here is my Page code :
#app.route('/upload', methods=['GET','POST'])
def upload():
uform = SomeOtherForm()
if uform.is_submitted() and uform.data['Usubmit']:
#Do stuff from previous form
# and declare array_of_choices
dmform = DataMappingForm() #Needs array_of_choices to work
...
What I've tried so far :
session['dm_choices'] gives me a working outside of request context error
global variables, get reset for some reason
overloading the __init__ of Form by adding the array but i can't access it in the parts above the __init__ function.
I should mention, this all needs to be on the same page.
Is there a way to pass this array_of_choices to my DataMappingForm class ?
EDIT This is what it looked like when I trid the __init__ overload:
class DataMappingForm(Form):
def __init__(self, dm_choices, *args, **kwargs):
self.dm_choices = dm_choices
Form.__init__(self, *args, **kwargs)
DMpatient_id = SelectField(u'Select Patient ID Column',
choices=dm_choices, validators=[Required()])
#I've tried putting it above or below, I get 'dm_choices is not defined'
I've Got it ! Thanks to #synonym for pointing me in the right direction with your last link.
All you need to do is declare a function in which the class is defined. You then pass the variable to the function, and it will be accessible within the class.
Finally, make the function return the form object.
Example :
def makeMyForm(myArray):
def class MyForm(Form):
my_select_field = SelectField(u'I'm a select field', choices=myArray)
my_submit = SubmitField(u'Go!')
return MyForm()
And to make the form, you use :
form = makeMyForm(theArrayYouWant)
And VoilĂ  !
Note : As I've had the problem before, I'll mention that the Array is composed of tuples :
myArray = [('value','What you see'),('value2','What you see again')]
If you want to dynamically change the choices of a SelectField the following should work:
class DataMappingForm(Form):
def __init__(self, choices)
self.DMpatient_id.choices = choices
DMpatient_id = SelectField(u'Select Patient ID Column') #note that choices is absent
If you want fully dynamic fields you can create the class dynamically in a function. From the WTForms Documentation:
def my_view():
class F(MyBaseForm):
pass
F.username = StringField('username')
for name in iterate_some_model_dynamically():
setattr(F, name, StringField(name.title()))
form = F(request.POST, ...)
# do view stuff
In that case you can customize the form as much as you want. Of course in the case you only want to customize the choices the first approach should be enough.

Django - If my template tag takes the context and the context is changed, are its new variables accessible in the rest of my page?

I have a template tag that takes the context. In it I add a variable to my context. It seems this variable prints correctly in the template which includes my template tag (let's call it "desktop"). But in the the template which includes THAT template, it no longer has the variable.
I've googled the django docs because I'm curious as to what's going on here. Is my template tag's context variable only available within the template that includes it and not beyond, as it seems?
In the Django library, django.template.base you have the parse_bits function. In this function, the view context is copied into a new variable.
if takes_context:
if params[0] == 'context':
params = params[1:]
else:
raise TemplateSyntaxError(
"'%s' is decorated with takes_context=True so it must "
"have a first argument of 'context'" % name)
And in the class InclusionNode class render function, a new context object is created to render the template of the template tag:
class InclusionNode(TagHelperNode):
def render(self, context):
"""
Renders the specified template and context. Caches the
template object in render_context to avoid reparsing and
loading when used in a for loop.
"""
resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
_dict = func(*resolved_args, **resolved_kwargs)
t = context.render_context.get(self)
if t is None:
if isinstance(file_name, Template):
t = file_name
elif isinstance(getattr(file_name, 'template', None), Template):
t = file_name.template
elif not isinstance(file_name, six.string_types) and is_iterable(file_name):
t = context.template.engine.select_template(file_name)
else:
t = context.template.engine.get_template(file_name)
context.render_context[self] = t
new_context = context.new(_dict)
# Copy across the CSRF token, if present, because
# inclusion tags are often used for forms, and we need
# instructions for using CSRF protection to be as simple
# as possible.
csrf_token = context.get('csrf_token', None)
if csrf_token is not None:
new_context['csrf_token'] = csrf_token
return t.render(new_context)
So it should not propagate the templatetag context to the calling template.

Render a Django CMS Placeholder to variable in view

I try to render a Django CMS Placeholder from a page to a variable, to return the rendered code as JSON.
So what I do is:
from cms.models.placeholdermodel import Placeholder
from cms.models.pagemodel import Page
def render(self, page_id, placeholder_slot, request):
page = Page.objects.get(id=page_id)
placeholder = page.placeholders.get(slot=placeholder_slot)
Now I want to render the placeholder to a variable. Which function do I have to call in which way to get this?
The Placeholder can be called manually and rendered like that, a few examples:
from cms import models
from django import template
def render(request):
placeholder = models.Placeholder.objects.get_or_create(slot='some_slot')
context = template.RequestContext(request)
return placeholder.render(request, width=None)
Or for json/javascript/etc. where you don't want full html but just the value:
def render_basic(request):
placeholder = models.Placeholder.objects.get_or_create(slot='some_slot')
context = template.RequestContext(request)
return placeholder.render(request, width=None, editable=False)
It's also possible (and even easier) to use the StaticPlaceholder:
from cms import models
def render(request):
placeholder = models.StaticPlaceholder.objects.get_or_create(name='name of the placeholder')
return placeholder.code
You can render it using Placeholder.render. Note that the context must contain a valid HttpRequest object under the 'request' key. width may be None. See the {% render_placeholder %} implementation for an example.

Django - Access Context Dictionary Before Template

I'm hoping to use a context processor or middleware to modify the values of the dictionary passed to render_to_response prior to the actual rendering. I have a messaging schema I'm trying to implement that would populate a message list based on the existence of a type of user that I'd like to search the context for prior to the rendering of the template.
Example:
def myview(...):
...
return render_to_response('template.html',
{'variable': variable},
)
and I'd like to be able to add additional information to the context on the existence of 'variable'.
How can I access 'variable' after my view defines it but before it gets to the template so that I can further modify the context?
use TemplateResponse:
from django.template.response import TemplateResponse
def myview(...):
...
return TemplateResponse(request, 'template.html',
{'variable': variable},
)
def my_view_wrapper(...):
response = my_view(...)
variable = response.context_data['variable']
if variable == 'foo':
response.context_data['variable_is_foo'] = True
return response
This is easy. If you have supplied just a little bit more code in your example the answer might have bit you.
# first build your context, including all of the context_processors in your settings.py
context = RequestContext(request, <some dict values>)
# do something with your Context here
return render_to_response('template.html', context)
Update to comment:
The result of a render_to_response() is an HTTPResponse object containing a template rendered against a Context. That object does not (to my knowledge) have a context associated with it. I suppose you could save the result of render_to_response() in a variable and then access the Context you passed it, but I'm not sure what problem you are trying to solve.
Did you modify the Context during rendering? If so you may find that the information is not there any longer because the Context has a scope stack which is pushed/popped during template processing.
You can create a dictonary for the context:
def myview(...):
c = dict()
c["variable"] = value
...
do some stuff
...
return render_to_response('template.html',c)
Maybe the RequestContext is the thing you are looking for.