Wagtail routablepageurl for global search functionlity - wagtail

I'm trying to add search functionlity in the header of the website so it's global but I'm having an issue with the context part of Routable Pages because I don't know how to set it so that it always links to the HomePage model no matter the URL.
For example, this works on the homepage seeing as self is the homepage.
{% routablepageurl self 'post_search' %}
However, if I go to the blog page (localhost:8000/blog/) it doesn't work because "self" should be the homepage. So with my limited knowledge I understand what the issue is but have no idea how to go about solving it.
I did try:
{% routablepageurl homepage 'post_search' %}
but I get the error
'str' object has no attribute 'relative_url'
I know I could go through every Model and pass in the HomePage into the context but surely there is a better way to do this?
The reason why I have added this to the HomePage is because I want the URL to be domain.com/search/
class HomePage(RoutablePageMixin, Page):
template = 'lr/home/home_page.html'
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context['no_webpage_title'] = True
return context
class Meta:
verbose_name = 'R HomePage'
#route(r'^search/$', name='post_search')
def post_search(self, request, *args, **kwargs):
context = self.get_context(request)
search_query = request.GET.get('q', None)
self.posts = BlogDetailPage.objects.live().public().order_by('-last_published_at')
if search_query:
self.posts = self.posts.search(search_query)
print(self.posts)
return render(request, 'global/search_results_page.html', context)

Simple answer: If you've decided that your search URL will be /search/ and there will be no reason ever to change it, then don't bother trying to get {% routablepageurl %} to output an answer you already know - just write <form action="/search/">.
More complex answer: Implement your search form as an inclusion tag. This will allow you to write a template for the search form, along with a Python function to set up the variables you want to pass to that template. Within that Python function, you can retrieve the HomePage instance (probably with HomePage.objects.first()), call reverse_subpage on it to retrieve the search URL, and pass that to the template as part of the context.

Related

How To Programmatically Set Up Wagtail Root Page For Tests Utilizing The StaticLiveServerTestCase Suite

This question is similar to another question here on stack overflow .
I am in the process of adding tests to my wagtail site utilizing Django's StaticLiveServerTestCase. Below is an example of the code base I have at hand:
class ExampleTest(StaticLiveServerTestCase):
def setUp(self):
self.browser = webdriver.Chrome()
def test_example_test(self):
self.assertContains("Contact Page", self.browser.content)
[...]
So when I run this test with python manage.py test, the test fails because I there is a 500 error. Please recall that I am using wagtail and NOT Vanilla Django alone. I am also using Django's Site framework as opposed to Wagtail's Site framework as allauth only allows for usage with Django's Site framework.
After applying the #override_settings(DEBUG=True) to the test like this:
#override_settings(DEBUG=True)
class ExampleTest(StaticLiveServerTestCase):
def setUp(self):
self.browser = webdriver.Chrome()
def test_example_test(self):
self.assertContains("Contact Page", self.browser.content)
[...]
The test still fails as the page that is being loaded is the wagtail default page.
My question is, how do I set up another page as the root/default wagtail page such that when a request to localhost:8000 [or any other port number given by the test server] is being made to the home page (i.e. http://localhost:8000/), I see that new page instead of the wagtail default page?
Thanks.
Since StaticLiveServerTestCase creates a new [temporary] "test" database [including running migrations and migrate], wagtail literally resets all sites and pages back to it's initial state after initial wagtail start [mysite] command.
This means that if you have any other Page that you would like to be the root page, you will have to hard code the instruction to do that.
Below is a way in which this can be achieved.
It is advisable to set these instructions within the setUpClass method of a class — usually a class Main() class where other test classes can inherit from; thereby encouraging D.R.Y.
class Main(StaticLiveServerTestCase):
#classmethod
def setUpClass(cls):
super(Main, cls).setUpClass()
cls.root = Page.objects.get(id=1).specific
cls.new_default_home_page = Index(
title="New Home Page Index",
slug="index",
)
cls.root.add_child(instance=cls.new_default_home_page)
cls.site = Site.objects.get(id=1)
cls.site.root_page = cls.new_default_home_page
cls.site.save()
cls.browser = Chrome()
Now my test classes (wherever they are) can inherit from this class and get the entire new home page setup instantly. For example:
# ExampleTest() inherits from Main() for ease of Wagtail Page setup: avoiding repetition of setUpClass().
class ExampleTest(Main):
def test_example_test(self):
self.assertContains("Contact Page", self.browser.title)
[...]
Hope this helps someone out there someday.
THIS SOLUTION IS VALID FOR: wagtail==2.7.4. anything above this version isn't guaranteed to work as wagtail's code base dictates. However, it's very unlikely that this wouldn't work.

Django Tastypie prevent file uri's being saved to a FileField

I've got a Django app with Tastypie, and mainly BackBone client side. One of my models has a few ImageFields. Here is a similar setup to help me explain the issue.
settings.py
MEDIA_URL = "/media/"
models.py
class Foo(models.model):
bar = models.ImageField()
baz = models.CharField()
api.py
class FooResource(ModelResource):
class Meta:
queryset=models.Foo.objects.all()
resource_name = "foo"
authorization = Authorization()
When I make a GET request to the API, it appends the MEDIA_URL to the file names to return the URI where a bar can be accessed. However, when I change the value of baz on a row, and then make a PUT request with that, it also changes the value for a bar to the URI. This means that the next time I GET the row, it appends the MEDIA_URL again, breaking the system and appending it for each successive GET and PUT. I end up with values for bar in the DB that look like.
/media/media/media/bar.jpg
I think I should fix this by overriding a method in my ModelResource, so that when there is a PUT request, it recognizes that it's getting either a URI or a real file, and alters its behavior in some way.
Is this the correct fix? Could you provide some implementation details of a fix?
I found the answer. Tastypie is well designed, similarly to Django. Unfortunately I was not familiar with the terminology so when I read the docs I didn't understand. You can easily modify behavior of the API at many levels. Here is my new API definition, which fixed the issue.
api.py
class FooResource(ModelResource):
class Meta:
queryset=models.Foo.objects.all()
resource_name = "foo"
authorization = Authorization()
def hydrate_bar(bundle):
bundle["bar"] = bundle["bar"].strip(MEDIA_URL)
return bundle
I should add that this only works for me because I exclusively POST my image files individually with a post_detail method which doesn't call this method. If I was to POST or PUT image files as part of the entire row, I expect this might raise an error if that isn't considered.

webapp2 jinja2 context_processor

I am building a project on GAE, webapp2, jinja2 and I use engineauth for authorization. I need something like Django's context_processor in order to use session, user and some other variables from webapp2.request in templates. Please, help me to solve this problem.
There are many ways to achieve this.
The simplest way probably looks like this:
def extra_context(handler, context=None):
"""
Adds extra context.
"""
context = context or {}
# You can load and run various template processors from settings like Django does.
# I don't do this in my projects because I'm not building yet another framework
# so I like to keep it simple:
return dict({'request': handler.request}, **context)
# --- somewhere in response handler ---
def get(self):
my_context = {}
template = get_template_somehow()
self.response.out.write(template.render(**extra_context(self, my_context))
I like when my variables are in template globals, then I can access them in my template widgets without having to pass around bunch of vars in template. So I am doing it like this:
def get_template_globals(handler):
return {
'request': handler.request,
'settings': <...>
}
class MyHandlerBase(webapp.RequestHandler):
def render(self, context=None):
context = context or {}
globals_ = get_template_globals(self)
template = jinja_env.get_template(template_name, globals=globals_)
self.response.out.write(template.render(**context))
There are other methods in: Context processor using Werkzeug and Jinja2

Hosting a dynamic RSS feed

I'm a newbie to GAE and am trying to work out how to host an RSS feed based on data stored on the service.
So far I can't work out an obvious way to do it having read through the docs.
Can anyone give me some pointers as to what API I should be using?
Why do you need an API? You can query for updated data using the normal query classes, and output the feed using the standard Python XML tools like ElementTree.
This isn't any different to serving a dynamic HTML page. Do it exactly the same way you would serve up an HTML template, but generate RSS or Atom instead of HTML.
The easiest way to get a quick solution is gae-rest but it is as old as 2008. You may find my solution helpful that renders my data to a GeoRSS template, so here's my solution:
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
<title>{{host}}</title>
<link href="http://{{host}}" rel="self"/>
<id>http://{{host}}/</id>
<updated>2011-09-17T08:14:49.875423Z</updated>
<generator uri="http://{{host}}/">{{host}}</generator>
{% for ad in ads %}
<entry>
<title><![CDATA[{{ad.title}}]]></title>
<link href="http://{{host}}/vi/{{ad.key.id}}"/>
<id>http://{{host}}/vi/{{ad.key.id}}</id>
<updated>{{ad.modified.isoformat}}Z</updated>
<author><name>{{ad.title|escape}}</name></author>
<georss:point>{{ad.geopt.lon|floatformat:2}},{{ad.geopt.lat|floatformat:2}}</georss:point>
<published>{{ad.added}}</published>
<summary type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">{{ad.text|escape}}</div>
</summary>
</entry>
{% endfor %}
</feed>
handler:
class GeoRSS(webapp2.RequestHandler):
def get(self):
start = datetime.datetime.now() - timedelta(days=60)
count = (int(self.request.get('count'
)) if not self.request.get('count') == '' else 1000)
try:
ads = memcache.get('ads')
except KeyError:
ads = Ad.all().filter('modified >',
start).filter('published =',
True).order('-modified').fetch(count)
memcache.set('ads', ads)
template_values = {'ads': ads, 'request': self.request,
'host': os.environ.get('HTTP_HOST',
os.environ['SERVER_NAME'])}
dispatch = 'templates/georss.html'
path = os.path.join(os.path.dirname(__file__), dispatch)
output = template.render(path, template_values)
self.response.headers["Cache-Control"] = "public,max-age=%s" % 86400
self.response.headers['Content-Type'] = 'application/rss+xml'
self.response.out.write(output)

Google App Engine logout url

I am having problems getting the logout link work in GAE (Python).
This is the page I am looking at.
In my template, I create a link
<p>Logout</p>
But when I click on it I get "broken link" message from Chrome. The url for the link looks like this:
http://localhost:8085/users.create_logout_url(
My questions:
Can anybody explain how this works in general?
What is the correct url for the dev server?
What is the correct url for the app server?
What is the ("/") in the logout url?
Thanks.
EDIT
This link works; but I don't know why:
<p>Logout</p>
What sort of templates are you using? It's clear from the output that you're not escaping your code correctly.
Seems to me that you want to do this instead:
self.response.out.write("This is the url: %s", users.create_logout_url("/"))
You could also pass it to your template, using GAEs implemented django templates.
from google.appengine.ext.webapp import template
...
...
(inside your request handler)
class Empty: pass
data = Empty()
data.logout = users.create_logout_url("/")
self.response.out.write(template.render(my_tmpl, {'data': data})
A useful approach is to add all sorts of info to a BaseRequestHandler and then use this as base class for all of your other request handler classes.
from google.appengine.ext import webapp
...
class BaseRequestHandler(webapp.RequestHandler):
def __init__(self):
webapp.RequestHandler.__init__(self) # extend the base class
class Empty: pass
data = Empty()
data.foo = "bar"
Then your new classes will have access to all the data you provided in the base class.
class OtherHandler(BaseRequestHandler):
def get(self):
self.response.out.write("This is foo: %s" % self.data.foo) # passes str "bar"
Hope it helps.
A.
Hi following more or less what this article is showing for the user account stuff. In gwt I store server side the logout/login url and I pass them to the client
http://www.dev-articles.com/article/App-Engine-User-Services-in-JSP-3002

Resources