Customizing Longclaw ProductIndex page - django

Longclaw/Wagtail newbie here. Wagtail CMS provides an overridable get_context method that makes it possible to pass dictionary values into the template. From the documentation:
class BlogIndexPage(Page):
...
def get_context(self, request):
context = super(BlogIndexPage, self).get_context(request)
# Add extra variables and return the updated context
context['blog_entries'] = BlogPage.objects.child_of(self).live()
return context
Longclaw is an eCommerce project built on top of Wagtail. Longclaw has an inbuilt Page model called ProductIndex. Is there any way to pass variables into the ProductIndex template, like I can with get_context?

Great question. It does not currently support this, but it would be great if it could. I recommend raising an issue on GitHub so we can't get this into development (https://github.com/JamesRamm/longclaw)
It may be possible to dynamically add the function;
def get_context(...):
....
ProductIndex.get_context = get_context
Ive not tried it so can't say for certain that it will work!

Related

How to Django reverse to pages created in Wagtail?

I have a Wagtail model for an object.
class ObjectPage(Page):
# fields, not really important
I want to make the object editable by front-end users, so I have also created a generic update view to accomplish this. My question is how I can use Django's reverse() function to point to the edited object in my get_success_url() method:
class EditObjectPage(LoginRequiredMixin, UpdateView):
# model, template_name, and fields defined; not really important
def get_success_url(self):
return("ObjectPage", kwargs={"slug" : self.object.slug}) # doesn't work
return("object_page_slug", kwargs={"slug" : self.object.slug}) # also doesn't work
I know how to do this when I'm explicitly defining the URL name in my urls.py file, but in this case, the URL is created as a Wagtail page and is handled by this line of my urls.py:
url(r"", include(wagtail_urls)),
I've been searching the documentation for whether Wagtail pages are assigned names that can be reversed to, but I'm finding nothing in the official documentation or here on StackOverflow.
The page object provides a get_url method - get_success_url should be able to accept this directly, so there's no need to use reverse.
def get_success_url(self):
return self.object.get_url()
Internally the URL route used by get_url is wagtail_serve, so if you absolutely need to go through reverse, you could replicate the logic there.

Add extra context variables into django oscar emails

Trying to add extra context variables for all django oscar email templates. The only way I've got it to work is overriding specific views like ProfileUpdateView. This method seems very messy, I'd have to override a lot of files. Is there a better way of doing this?
From checking the source code, the ProfileUpdateView uses Django's Class Based View FormView, which in turn implements the get_context_data method allowing the injection of extra data in the view.
You can simply create a view inehiriting ProfileUpdateView and override the get_context_data:
class MyProfileUpdateView(ProfileUpdateView):
...
def get_context_data(self, **kwargs):
context = super(MyProfileUpdateView, self).get_context_data(**kwargs)
context['somevar'] = SomeQueryMaybe.objects.all()
return context
Ended up making a custom management command to do it manually since the need to change the emails templates would be really rare.

How to customize third-party package in django

I couldn't find anywhere an explicit guide that will show what is the correct way. For example, I am using a package django-two-factor-auth for my django website. I wanted to add a context variable to the template and display it. I created a folder two_factor inside of my templates folder and the template file with the same name. That part is easy. But I also needed to inherit generic view to add my context (of course, I don't want to change source code of the third-party package). For this I created a new app inside my project and called it two_factor_custom and added following code to views.py:
from binascii import unhexlify
from base64 import b32encode
from two_factor.views.core import SetupView
class SetupViewCustom(SetupView):
def get_context_data(self, form, **kwargs):
context = super(SetupViewCustom, self).get_context_data(form, **kwargs)
if self.steps.current == 'generator':
key = unhexlify(self.get_key('generator').encode('ascii'))
context.update({
'secret': b32encode(key).decode('ascii')
})
return context
I would appreciate if you could say that this is the correct way to extend or override some of the behaviour of third-party packages. If not what I am doing wrong?
You can extend the template context by adding your own template context processor. See also the Django documentation.
The TEMPLATE_CONTEXT_PROCESSORS setting is a tuple of callables – called context processors – that take a request object as their argument and return a dictionary of items to be merged into the context.

Passing template variables from URL to FormPreview in Django

I'm a Django noob and fear the answer to my question is fairly obvious, but hoping someone can help.
I'm building an app that includes the same form on every page, with the content surrounding the form and the model instance to which the form data is tied dependent on a value passed in the URL. Works fine using the standard Form class and (URL, 'template.html', myapp.view) in URLconf, like so:
url(r'^listings/(?P<listing_id>\d+)/submit$', 'myapp.views.lister'),
With FormPreview, however, instead of calling the view in the URLconf, you're calling the subclass with the view functionality baked in.
url(r'^listings/(?P<listing_id>\d+)/submit$', PickFormPreview(PickForm)),
From what I can gather from the docs, FormPreview uses parse_params to pass values captured in the URL to state.self, which I believe is a dictionary. Unfortunately given my level of experience, I can't figure out from this barebones understanding how to customize my FormPreview subclass how to pass the listing_id captured in the URL to a template variable in my form.html template called by FormPreview. Do I somehow need to override parse_params? Or somehow pass state.listing_id? Or am I missing it entirely?
Any help much appreciated!
You're on the right track. The parse_params method in FormPreview does not do anything, but it is the correct method to override in your subclass.
The following example saves listing_id to self.state.listing_id, which is available in the template context.
class PickFormPreview(FormPreview):
def parse_params(self, *args, **kwargs)
"""
Saves listing_id from the url to state
"""
self.state['listing_id'] = kwargs['listing_id']
Then in your template, you access it as:
Listing {{ state.listing_id }}

Is there before_filter in django as in rails?

Is there any feature available in django so that we can combine some filtering on all actions in the view of django, like before_filter: is available in rails.
I'm still learning Rails but from what I've observed so far python decorators also seem to be used in Django in a very similar way to the before_filter in Rails.
Here's one example of it's usage in authenticating users: https://docs.djangoproject.com/en/1.2/topics/auth/#the-login-required-decorator
No. before_, around_ and after_ filter concepts aren't present in Django, but it isn't hard to write your own functions to do the same thing. There are also signals and generic views that might accomplish what you're needing to do.
decorators can be used for this. your decorator can be the before_filter or after_filter depending on how whether it calls the decorated function first or last.
here is an example
#question_required
def show(request, question_id):
return HttpResponse(f'you are looking at {question_id}')
Here we have decorated the show function with question_required and want it to act as a before_filter. so we will define the decorator like this:
def question_required(func):
def containing_func(*args, **kwargs):
request = args[0]
question_id = kwargs['question_id']
try:
question = Question.objects.get(pk=question_id)
except Exception as e:
raise e
return func(*args, **kwargs)
return containing_func
As you can see above the decorator is first checking for the question to exist in the db. If it exists it calls the actual show function or it raises an exception.
In this way it acts like a before filter does in rails.