Wagtail: Dynamically load StreamField blocks in the admin EditView - django

I have a use case where we need dynamic blocks for a StreamField. Unfortunately this doesn't seem possible, since block types are specified in the model's StreamField field.
I dug through the FieldPanel, StreamBlock and GroupPanel code and cannot clearly find a way to override the available blocks for the StreamField outside the model definition.
This is my current admin EditView, where you can see what I'm trying to accomplish (see the NOTE/FIXME comments below:
class EditProductView(EditView):
def get_edit_handler(self):
summary_panels = [
FieldPanel('title'),
FieldPanel('description'),
FieldPanel('body'),
]
attributes_panel = [
# NOTE/FIXME: This is where we need the custom blocks to load, based
# on `self.instance.product_type`.
FieldPanel('attributes'),
# StreamFieldPanel (which is deprecated) does not work because you can
# only set the blocks within the model, when defining the StreamFieldPanel.
# I would love to be able to do something like:
StreamFieldPanel('attributes', block_types=[
('test', blocks.CharBlock()), # Like this for example
])
]
settings_panels = [
FieldPanel('categories'),
StateMachinePanel('state', sm_prop='sm', heading="State"),
]
return TabbedInterface([
ObjectList(summary_panels, heading='Summary'),
ObjectList(attributes_panel, heading='Attributes'),
ObjectList(settings_panels, heading='Settings'),
]).bind_to_model(self.model_admin.model)
Is it possible to override available StreamField blocks in this way, or are there potential issues with json_field DB integrity?

This wouldn't work, because the StreamField block definition isn't just used for the edit form - any time you access that field, even just to output it on a template, the StreamField needs to look at the block definition to unpack the JSON and return the appropriate Python objects. For example, suppose the database contained:
[{'type': 'test', 'value': 123}]
What should page.attributes[0].value return? If the test block is of type IntegerBlock, it would return 123 - but if it was a PageChooserBlock instead, it would return the Page instance with ID 123. If it was an ImageChooserBlock, it would return the Image instance with ID 123, and so on. In your example, the only way to find out the block type is to call get_edit_handler and construct the entire editing interface as a side effect - this would be mixing up model-level logic with admin interface code, and be massively inefficient just for accessing a single field.

Related

In Django, How to get the currently running app from within a model on another app?

Basically i am trying to use .get_absolute_url() to return dynamic links in relative to the current app running, in other words reverse the model url to a different url based on which app being called.
Let me try to explain what i am trying to do by example, We have three apps
Blog (serves as a shared data layer, contains models for post, authors ... etc)
BloggerWay (serves as a view layer number one, uses the Blog app models to display content in some given layout)
TumblrWay (serves as another view layer, again uses the Blog app models to display content in some given layout)
My urls.py files goes like
----------
*Project.urls.py*
----------
urlpatterns= [
url('/blogger/', include(BloggerWay.urls.py, namespace='blogger'),
url('/tumblr/', include(TumblrWay.urls.py, namespace='tumblr'),]
----------
*BloggerWay.urls.py*
----------
urlpatterns= [
url('/post/(?P<id>\d+)', Blog.as_view(), name='blog'),]
----------
*TumblrWay.urls.py*
----------
urlpatterns= [
url('/post/(?P<id>\d+)', Blog.as_view(), name='blog'),]
My question is How can i define .get_absolute_url() method for the Post model so it knows which app we are in and return the correct url to it.
example:
if in BloggerWay return '/blogger/post'
if in TumblrWay return '/tumblr/post'
I know that i can use the reverse() function to get the url for a given named pattern and that it accepts a current_app= argument but my problem is how can i get the running app so i can pass it.
class Post(...):
def get_absolute_url(self):
WhoAmI = ... #get current app here, BUT HOW!
return reverse('post', current_app=WhoAmI)
Solutions that i want to avoid:
I can inherit the Post class in both of the apps, override the .get_absolute_url() there, hardcoding the name space of each app in it. Then use the class in my app instead of directly using the one defined as model/table.(while offcourse avoid performing migrations for that class, even better define it somewhere else than models.py)
Implement another property for get_url() for your class.
The reverse function should includes "namespace:name"
e.g
class Post(...):...
def get_blogger_url(self):
return reverse("blogger:post", kwargs={"id":self.id}) # for Blogger
def get_tumblr_url(self):
return reverse("tumblr:post", kwargs={"id":self.id}) # for Tumblr

Django context processors and URL arguments

I have some code that is repeated at the start of my Django views. It basically just adds some variables to the context, but based on the URL argument, e.g.
def someView(request, id):
target = Target.objects.get(id=id)
# name will be added to ctx
name = target.name
(there are more attributes added and other attributes from related models, but this gives the general idea --- There are quite a few lines of repeat code at the start of each view)
I thought I could make my code more DRY by taking advantage of Django's context processors, but it would seem these don't access to the URL arguments?
Is there another way to avoid these repeat lines? Maybe middleware or something else?
You can access the URL parameters via request through the resolver_match attribute. So for instance you can do request.resolver_match.kwargs['id'] to get the ID kwarg.

django settings variables get lost in while being passed to templates

i have a weird problem.
Basically, in my settings.py file i have 4 variables
URL_MAIN = 'http://www.mysite'
URL_JOBS = 'http://jobs.mysite'
URL_CARS = 'http://cars.mysite'
URL_HOMES = 'http://homes.mysite'
In my views.py i have the usual:
from settings import *
I have 6 views calling them and just returning them to templates inside the context:
class CarsHp(TemplateView):
...
class JobsHp(TemplateView):
...
class HomesHp(TemplateView):
...
class CarsList(TemplateView):
...
class JobsList(TemplateView):
...
class HomesList(TemplateView):
...
which are being called in urls by
CarsList.as_view()
...
All of those views have the same statement:
context['URL_MAIN'] = URL_MAIN
...
for all 4 variables.
In templates i'm correctly getting all 4 of them, except for URL_MAIN, which "gets lost" in 2 of those 6 views. I'm accessing them with classical {{ URL_MAIN }} and i've been trying everything, from moving to renaming, but still that URL_MAIN doesn't show up (i get empty string, no errors of sort) after being served from 2 of those views. All the functions basically share the same code (except for the querying and data processing part) and those settings' variables are just being assigned and returned off. Not any sort of check nor modification. I've been trying with django's shell, and i could always retrieve them.
We're being served by apache, with some proxypassing configurations for the robots.txt file and static files. Nothing "serious".
I'm not posting all the 6 views source codes just because they're long and the relevant parts are all described above. But i can post them if you want,i just don't know if it is actually useful since i've been triple checking all the sources for clashing on names or double declarations or incorrect use.
Thanks all in advance, this is really stunning my brain
Ideally, you should use template context processors for this. It will cut down your code and allow you to see exactly where the problem is.
Make a file in your projects called urls_context_processor.py (or similar) and put your variables in there:
def common_urls(request):
return {
'URL_MAIN': "http://...",
'URL_JOBS': "http://...",
'URL_CARS': "http://...",
'URL_HOME': "http://...",
}
and in your settings.py
TEMPLATE_CONTEXT_PROCESSORS = = (
....
'my_project.urls_context_processor.common_urls',)
now the urls variables will be automatically available in all your template, and you won't need to hard code them into every view.

How to serialize to json format a queryset that use the 'extra' statement in Django?

I want to serialize a QuerySet that contains an extra statement:
region_list = Region.objects.extra(select={ 'selected': 'case when id = %s then 1 else 0 end' % (new_region.id)}).all()
I use the statement below to serialize
return HttpResponse(serializers.serialize('json', region_list), mimetype='application/json')
But when I obtain the json results in the browser, only the fields of the Region model appears, the selected field dissapear.
How can I fix that?
One slightly longwinded solution would be to to dump the objects to JSON via django-piston's JSONEmitter class. When you register your Region model with piston, you can say what fields to include, and mention 'selected' there, and then use your annotation to make sure that the queryset used in the piston handler contains all the info you want.
Or just look at how piston does it and, if you don't want all of piston, just mimic the bits you do.

Adding more CoC to Django

I come from a Cake background, and I'm just starting to learn Django now. I'm liking it quite a bit, but I kinda wish it used convention over configuration like cake does. So,
How can I get Cake-style URLs automatically? For example, if I went to mysite.com/posts/view/5 it would load up mysite.posts.views.view and pass an argument 5 to it? I was thinking I could add something like (r'^(.*)/(.*)', 'mysite.$1.$2'), to urls.py, but of course, that won't work.
How can I automatically load up a template? Each view function should automatically load a template like templates/posts/view.html.
Is this even possible, or do I have to hack the core of Django?
Here's my solution, based on what Carl suggested:
urlpatterns = patterns('',
# url pats here
url(r'^(?P<app>\w+)/(?P<view>\w+)/(?P<args>.*)$', 'urls.dispatch')
)
def dispatch(req, app, view, args): # FIXME: ignores decorators on view func!
func = get_callable(app+'.views.'+view)
if args:
ret = func(req, *args.split('/'))
else:
ret = func(req)
if type(ret) is dict:
return render_to_response(app+'/'+view+'.html', ret)
else:
return ret
Seems to be working pretty well with initial tests. Solves both problems with a single function. Probably won't support GET-style arguments tho.
Those points are both implementable without hacking Django core, but either one will require a non-trivial level of familiarity with advanced Python techniques.
You can do the generic URL pattern with a pattern like this:
url(r'^(?P<appname>\w+)/(?P<viewfunc>\w+)/(?P<args>.*)$', 'myresolverfunc')
Then define a 'myresolverfunc' "view" function that takes "appname", "viewfunc", and "args" parameters, and implement whatever logic you want, splitting args on "/" and dynamically importing and dispatching to whatever view function is referenced. The trickiest part is the dynamic import, you can search Django's source for "importlib" to see how dynamic imports are done internally various places.
The automatic template loader can be implemented as a view function decorator similar to the various "render_to" decorators out there, except you'll generate the template name rather than passing it in to the decorator. You'll have to introspect the function object to get its name. Getting the app name will be trickier; you'll probably just want to hardcode it as a module-level global in each views.py file, or else work in conjunction with the above URL dispatcher, and have it annotate the request object with the app name or some such.
I don't you'll need to hack the core of Django for this. It sounds like you might be in need of generic views. Also check out the Generic Views topic guide.
The first example given in the generic views documentation sounds like your first bullet point:
Example:
Given the following URL patterns:
urlpatterns = patterns('django.views.generic.simple',
(r'^foo/$', 'direct_to_template', {'template':'foo_index.html'}),
(r'^foo/(?P<id>\d+)/$', 'direct_to_template', {'template':'foo_detail.html'}),
)
... a request to /foo/ would render the template foo_index.html, and a request to /foo/15/ would render the foo_detail.html with a context variable {{ params.id }} that is set to 15.