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

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.

Related

Change where a Page is displayed in the URL

I have setup my website whereby the main parental/tree structure is Home > Shop > Category > Product" with > denoting parent of.
This works fine, however when accessing a Product(Page) the url is automatically (and correctly) configured at /shop/test-category/test-product by Wagtail.
I would like to change it so that the product is actually displayed as being at the root level (even though it isn't). So if a user accesses Test Product it would be at /test-product/.
Looking through the docs, the RoutablePageMixin seems like it might do the trick but I have no idea how to go about implementing it. Any ideas?
This solution will make the product available at both URLS:
/shop/test-category/test-product/
/test-product/
Approach:
You are correct that you will need to use RoutablePageMixin, be sure to install it in your installed_apps as per the instructions before importing.
The example below adds RoutablePageMixin to your HomePage, as this is the page that will be located at the root / URL. We do a regex check and match for a single slug before a trailing /.
Then we see if we can find a ProductPage with that slug, and serve (or redirect) to that page. Finally, if there is no match, we call the home_page's serve method with the current request to handle anything else. This may be an incorrect URL or a correct sub-page URL.
Caveats:
If you have a sub-page at the same slug as a product page, the sub-page will never load, there are no smarts in this code to work around that. You could put some logic in the validation of product names and sub-page names if this becomes an issue in the future.
This does not consider SEO issues, search engines will treat these as different pages and hence you may want to think about declaring your canonical URL in your meta tags.
This does not redirect back from /shop/test-category/test-product/ to /test-product/ - that can be done by overriding the serve method on your ProductPage and redirecting to something like home_page.url + '/' + self.slug.
Example Code:
# models.py - assuming all your models are in one file
from django.db import models
from django.shortcuts import redirect # only needed if redirecting
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.contrib.wagtailroutablepage.models import RoutablePageMixin, route
from wagtail.core.models import Page
class ProductPage(Page):
price = models.DecimalField(max_digits=5, decimal_places=2)
content_panels = Page.content_panels + [
FieldPanel('price')
]
class HomePage(RoutablePageMixin, Page):
#route(r'^(?P<product_slug>[\w-]+)/$')
def default_view(self, request, product_slug=None):
"""Route will match any `my-product-slug/` after homepage route."""
product_page = Page.objects.exact_type(ProductPage).filter(slug=product_slug).first()
if product_page:
# option 1 - redirect to the product's normal URL (non-permanent redirect)
# return redirect(product_page.specific.url)
# option 2 - render the product page at this URL (no redirect)
return product_page.specific.serve(request)
else:
# process to normal handling of request so correct sub-pages work
return self.serve(request)

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.

NDB Query not returning most recently added object

I have a GAE website. The home page displays a list of project objects and a form for adding more projects. When the submit button is pressed, an ajax request is made which runs the 'create_userstoryproject()' method. This method adds the new project and returns a list of all projects, including the one that was just added. For some reason, the query in the 'create_userstoryproject()' method does not return the project just added. However, if I refresh the page, causing the 'get()' method to run, the newly added project shows up just fine. I haven't simplified this code--it's cut and pasted. Anyone have any idea why the newly created project doesn't show up until I refresh the page?
class Home(BaseApp): #BaseApp is derived from webapp2.RequestHandler
def get(self):
self.context['userstoryprojects'] = UserStoryProject.query().order(UserStoryProject.Order)
self.context['userstorystatuses'] = UserStoryStatus.query().order(UserStoryStatus.Order)
self.render('/userstories')
def create_userstoryproject(self):
description = self.request.get('userstoryproject[description]', default_value=None)
if description:
userstoryproject = UserStoryProject()
userstoryproject.Description = description
userstoryproject.put()
self.context['userstoryprojects'] = UserStoryProject.query().order(UserStoryProject.Order)
self.render('/userstoryprojects')
else:
self.write("fail.")
This is because of eventual consistency.

Django Models "IndexError: list index out of range" Pydev

I have a Django project in Eclipse PyDev.
I have a file views.py which has the line:
from models import ingredient2
In models.py I have:
from django.db import models
class ingredient2(models.Model):
ingredient = models.CharField(max_length=200)
When I try to run the app I get the following error:
File "C:\Python27\lib\site-packages\django\db\models\base.py", line 54, in __new__
kwargs = {"app_label": model_module.__name__.split('.')[-2]}
IndexError: list index out of range
I did sync the database and started the server running.
I went into base.py and added 2 print statements (yes, I probably should not edit Django's files):
if getattr(meta, 'app_label', None) is None:
# Figure out the app_label by looking one level up.
# For 'django.contrib.sites.models', this would be 'sites'.
model_module = sys.modules[new_class.__module__]
print model_module #ADDED
print model_module.__name__ #ADDED
kwargs = {"app_label": model_module.__name__.split('.')[-2]}
They print out:
<module 'models' from 'C:\Users\Tine\workspace\slangen\slangen2\bolig\models.pyc'>
models
manage.py is contained within the bolig folder. I think the correct app label would be "bolig". The app worked several months ago and now, when I come back to it, something is not right. I have been creating other projects in PyDev.
Add a meta class with an app_label inside your model class definition:
class Foo:
id = models.BigIntegerField(primary_key=True)
class Meta:
app_label = 'foo'
I had something similar
instead of
from models import ingredient2
try :
from your_app_name.models import ingredient2
Well, not really an answer, but... I ended up creating a new django project and then copying in my code. That fixed the problem.
I was also getting the kwargs = {"app_label": model_module.__name__.split('.')[-2]} error when using PyDev. In my case, the project wasn't refreshed before I tried to run it. As soon as I refreshed it, all was well again.
I ran into this problem using Eclipse, Django and PyDev. I needed to have the application (instead of some .py file for example) selected in the PyDev Package Explorer (left panel) before clicking Run for everything to work properly.
in my case, models.py contains models
when I import models to other .py, say views.py it doesn't raise error when I run views.py
but when I run models.py, it raise the same error.
so I will just don't run in the models.py

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