Custom URL for Wagtail model - django

I have a model like this:
from wagtail.wagtailcore.models import Page
class Blog(Page):
created = models.DateTimeField(auto_now_add=True)
...
..
Right Now, my default, if my slug is hi-there, the blog post is accessible on site_url:/hi-there/ but I want it accessible via site:url/2014/02/05/hi-there/ The page has various methods like url, url_path which one should I override and what's the best practice to achieve something like this in wagtail?

The RoutablePageMixin is the current (v2.0+) way to accomplish this.
Add the module to your installed apps:
INSTALLED_APPS = [
...
"wagtail.contrib.routable_page",
]
Inherit from both wagtail.contrib.routable_page.models.RoutablePageMixin and wagtail.core.models.Page, then define some view methods and decorate them with the wagtail.contrib.routable_page.models.route decorator:
from wagtail.core.models import Page
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
class EventPage(RoutablePageMixin, Page):
...
#route(r'^$') # will override the default Page serving mechanism
def current_events(self, request):
"""
View function for the current events page
"""
...
#route(r'^past/$')
def past_events(self, request):
"""
View function for the past events page
"""
...
# Multiple routes!
#route(r'^year/(\d+)/$')
#route(r'^year/current/$')
def events_for_year(self, request, year=None):
"""
View function for the events for year page
"""
...

New in Wagtail v0.5 is a mechanism that directly addresses this sort of thing:
Embedding URL configuration in Pages or RoutablePage in v1.3.1
(the docs even have an Blog example!)

I was looking into migrating my blog to a wagtail version and wanted to support my previous url schema and had to solve this exact problem. Luckily I just found a solution and want to share it, hopefully this will be helpful for someone else in the future.
The solution is a 2 Step process.
change the url of a blog page to contain the date as well.
class Blog(Page):
created = models.DateTimeField(auto_now_add=True)
def get_url_parts(self, request=None):
super_response = super().get_url_parts(request)
# handle the cases of the original implementation
if super_response is None:
return None
(site_id, root_url, page_path) = super_response
if page_path == None:
return super_response
# In the happy case, add the date fields
# split prefix and slug to support blogs that are nested
prefix, slug, _ = page_path.rsplit("/", 2)
return (
site_id,
root_url,
f"{prefix}/{self.created.year}/{self.created.month}/{self.created.day}/{slug}/",
)
And now we need to make those posts also route-able.
class BlogIndexPage(RoutablePageMixin, Page):
...
def route(self, request, path_components):
if len(path_components) >= 4:
year, month, day, slug, *rest = path_components
try:
subpage = self.get_children().get(slug=slug)
return subpage.specific.route(request, rest)
except Page.DoesNotExist:
...
return super().route(request, path_components)
This solution ignores the date and just use the slug to locate a blog post as the original solution. This should also work when you don't use the RoutablePageMixin.
Hope this is still helpful for someone.

Related

django-rest-swagger UI doesn't have form for POST request body (function based view)

I have this function-based view with django-rest-swagger decorated. However, I can't find place in UI that allow me to post the payload (request.body).
I saw a couple solutions about doing it with class-based view, but I was wondering if there is a way to do it with function-based view.
Thank you in advance!
#renderer_classes([JSONRender])
#api_view(['POST'])
def some_method(request):
body = json.loads(request.body)
return JsonResponse({'status': 'ok'})
I am gonna answer my question since the django-rest-swagger was deprecated in June 2019 and I just found out 2 feasible solutions.
First one will change the UI globally.
In ping.views (or any other location you wish) add following class.
from rest_framework.schema import AutoSchema
class CustomSchema(AutoSchema):
def __init__(self):
super(CustomSchema, self).__init__()
def get_manual_fields(self, path, method):
extra_fields = [
coreapi.Field('command', required=True, location='form', schema=String(), description='', type='', example='',
coreapi.Field('params', required=False, location='form', schema=String(), description='', type='', example='',
]
manual_fields = super().get_manual_fields(path, method)
return manual_fields + extra_fields
Add following settings in settings.py of your Django project.
REST_FRAMEWORK = {
# Corresponding path to where you added the class
'DEFAULT_SCHEMA_CLASS': 'ping.views.CustomSchema',
}
Second solution can be applied on a per-view basis. You may check here for official guide
Use #schema from rest_framework.decorators.schema to overwrite DEFAULT_SCHEMA_CLASS.
#api_view(['POST'])
#schema(CustomSchema())
def your_view(request):
print(request.body)
return JsonResponse({'task_status': 200'})
Basically, the idea is to overwrite DEFAULT_SCHEMA_CLASS. The word schema is the term that they used to refer to swagger UI for each view in rest_framework.
When you use #api_view() to decorate your function-based view, it will assign your function an attribute schema with value APIView.schema from rest_framework.views.APIView.
rest_framework.views.APIView will in further call DefaultSchema() to load the DEFAULT_SCHEMA_CLASS from your REST_FRAMEWORK configuration in settings.py.
Without other specifying, DEFAULT_SCHEMA_CLASS is rest_framework.schemas.openapi.AutoSchema by this official announcement. You might want to change it to rest_framework.schemas.coreapi.AutoSchema since it is the one that compatible with django_rest_swagger.
Hope this tutorial helps people who use django-rest-swagger (2.2.0) with function-based views for their Django project.
Please leave comments if there are anything I can help on this issue.

Is there a way to hide part of a URL created from the Wagtail admin page tree?

Let's say I've created a blog for my website. The tree structure setup in the Wagtail admin looks like this:
Homepage > Blog Index > Blog Post
Is it possible to keep the Blog Index page in the admin page tree but remove it from the URL so that my URL's look like this:
Homepage > Blog Post
I am assigning a custom group to the Blog Index page which allows them to edit only Blog Posts that they have created, which is why Blog Index needs to stay in the tree on the admin side.
I've done a little work with the routablepagemixin but not to eliminate anything from the URL, only add to it.
I'm not entirely certain if a RoutablePageMixin is the right way to go about this, but that's how I've solved this before.
Here's an example of how you could do it with a RoutablePageMixin and a route (Note: I chopped this together pretty quickly and didn't test it, you might need to do some adjusting)
from django.http import HttpResponseRedirect
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from wagtail.core.models import Page
from blog.models import BlogPage
class HomePage(RoutablePageMixin, Page):
"""A home page class."""
# HomePage Fields here...
# This route will collect the blog slug
# We'll look for the live BlogPost page.
#route(r"^(?P<blog_slug>[-\w]*)/$", name="blog_post")
def blog_post(self, request, blog_slug, *args, **kwargs):
try:
# Get the blog page
blog_page = BlogPage.objects.live().get(slug=blog_slug)
except BlogPage.DoesNotExist:
# 404 or post is not live yet
return HttpResponseRedirect("/")
except Exception:
# Handle your other exceptions here; here's a simple redirect back to home
return HttpResponseRedirect("/")
# Additional logic if you need to perform something before serving the blog post
# Let the blog post page handle the serve
return blog_page.specific.serve(request, *args, **kwargs)
One other thing to note: you'll want to change the sitemap url on your original blog post pages so they don't show up as /blog/blog-slug/ inside of sitemap.xml.

How can I hit Django api with parameters using Postman?

#must_be_admin_user
def patch(self,request,category_id):
'''
Updates Data in the Category
Parameters
Authenticated Admin User , category_id
'''
category = get_object_or_404(Category, id=category_id)
# IF admin is not the Creater of the Category
if category.created_by != request.user.id:
Response(status=status.HTTP_400_BAD_REQUEST, data={"error": "You did not Create this so you cant delete this"})
category.last_updated = timezone.now()
updated_category = CategorySerializer(
category, data=request.data, partial=True)
if updated_category.is_valid():
updated_category.save()
return Response(status=status.HTTP_201_CREATED, data=updated_category.data)
return Response(status=status.HTTP_400_BAD_REQUEST, data={"error": updated_category.errors})
I want to know how to send category_id using postman .
I am facing issues hitting this api using postman
If your the URL pattern for this view is called, for instance 'patch', you could:
from django.urls import reverse
# Use the resulting url in Postman. This is what I use in tests.
url = reverse('patch', args=(some_id_here,), kwargs={})
You can use this technique for every view you have.
More here
Another thing you have to be aware of is that the user performing this request has to be logged in as admin, so you have set Postman cookies accordingly.
I recommend the use of a google chrome extension called "Postman Interceptor"
Good luck!

Sitemap and get_absolute_url

i make my sitemap and have an interrogation about to get only my objects that have a valid url, explain :
for exemple my events can have divers url, a dedicated page, a simple link to a pdf, a redirection to other page of my site or other site or simply no url.
In my sitemap i do this for only get event that have an url :
def items(self):
events = Event.objects.all()
event_array = []
for event in events:
if event.get_absolute_url():
event_array.append(event)
return event_array
That's work, but i have look at model managers and i think it can do this for me too, so my question is : it is better to have a model manager for this or my way to do this is good?
Thanks :)
Yes, your Model Manager is here to do jobs like this. Create a method that filter all event with an url.
Read this part of the documentation for more details : Django 1.8 - Model Manager
E.g :
from django.db import models
class EventManager(models.Manager):
def get_queryset(self):
return super(EventManager, self).get_queryset().all()
def with_url():
return self.get_query_set().exclude(url__isnull=True, url__exact='')
class Event(models.Model):
objects = EventManager()

Getting Google App Engine blob info in Django view

This is a follow up question for Django on Google App Engine: cannot upload images
I got part of the upload of images to GAE Blobstore working. Here's what I did:
In models.py I created a model PhotoFeature:
class PhotoFeature(models.Model):
property = models.ForeignKey(
Property,
related_name = "photo_features"
)
caption = models.CharField(
max_length = 100
)
blob_key = models.CharField(
max_length = 100
)
In admin.py I created an admin entry with an override for the rendering of the change_form to allow for insert of the correct action to the Blobstore upload url:
class PhotoFeatureAdmin(admin.ModelAdmin):
list_display = ("property", "caption")
form = PhotoFeatureForm
def render_change_form(self, request, context, *args, **kwargs):
from google.appengine.ext import blobstore
if kwargs.has_key("add"):
context['blobstore_url'] = blobstore.create_upload_url('/admin/add-photo-feature')
else:
context['blobstore_url'] = blobstore.create_upload_url('/admin/update-photo-feature')
return super(PhotoFeatureAdmin, self).render_change_form(request, context, args, kwargs)
As I use standard Django, I want to use the Django views to process the result once GAE has updated the BlobStore in stead of BlobstoreUploadHandler. I created the following views (as per the render_change_form method) and updated urls.py:
def add_photo_feature(request):
def update_photo_feature(request):
This all works nicely but once I get into the view method I'm a bit lost. How do I get the Blob key from the request object so I can store it with PhotoFeature? I use standard Django, not Django non-rel. I found this related question but it appears not to contain a solution. I also inspected the request object which gets passed into the view but could not find anything relating to the blob key.
EDIT:
The Django request object contains a FILES dictionary which will give me an instance of InMemoryUploadedFile. I presume that somehow I should be able to retrieve the blob key from that...
EDIT 2:
Just to be clear: the uploaded photo appears in the Blobstore; that part works. It's just getting the key back from the Blobstore that's missing here.
EDIT 3:
As per Daniel's suggestion I added storage.py from the djangoappengine project which contains the suggested upload handler and added it to my SETTINGS.PY. This results in the following exception when trying to upload:
'BlobstoreFileUploadHandler' object has no attribute 'content_type_extra'
This is really tricky to fix. The best solution I have found is to use the file upload handler from the djangoappengine project (which is associated with django-nonrel, but does not depend on it). That should handle the required logic to put the blob key into request.FILES, as you'd expect in Django.
Edit
I'd forgotten that django-nonrel uses a patched version of Django, and one of the patches is here to add the content-type-extra field. You can replicate the functionality by subclassing the upload handler as follows:
from djangoappengine import storage
class BlobstoreFileUploadHandler(storage.BlobstoreFileUploadHandler):
"""Handler that adds blob key info to the file object."""
def new_file(self, field_name, *args, **kwargs):
# We need to re-process the POST data to get the blobkey info.
meta = self.request.META
meta['wsgi.input'].seek(0)
fields = cgi.FieldStorage(meta['wsgi.input'], environ=meta)
if field_name in fields:
current_field = fields[field_name]
self.content_type_extra = current_field.type_options
super(BlobstoreFileUploadHandler, self).new_file(field_name,
*args, **kwargs)
and reference this subclass in your settings.py rather than the original.