Include a custom field in every Wagtail API response - wagtail

My company is running Wagtail headless, using only the API, to power parts of an existing web intranet. We'd like to include a customized "edit bar" at the top of every page in the main web application, which points to the "edit" page of the matching record in Wagtail. We're going to pass the current user's along with the request. Then we'd like to include a custom field in the Wagtail API response, for all requests which indicates that user's permission to edit that resource.
To illustrate, I'm looking to make a request like this:
http://localhost:32891/api/v2/page/?fields=place_id,location_slug&type=destination.DestinationPage&user_email=foo#bar.com
Which would result (in a perfect world) in a response like this:
{
"custom": {
"can_edit": True,
},
"meta": {
"total_count": 10
},
"items": [
{
"id": 1,
"title": "Test blog post",
"published_date": "2016-08-30",
},
]
}
The API indicates that you can include custom fields in the Page (or Image and Document), API response, but ideally I'd like for this object to be available for all "things" via our API. This means that if someone requests a document, I wouldn't have to manually return this field for each individual model.
I'm thinking it might be possible to override the behavior of the BaseAPIEndpoint?

Here's one way that we figured out how to do it. The "SecuredPagesAPIEndpoint" pages class already existed in our system.
class SecuredPagesAPIEndpoint(PagesAPIEndpoint):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def listing_view(self, request):
response = super().listing_view(request)
# create custom response object
# this object will contain any number of custom properties that we want to
# expose to consumers of this API
response.data['custom'] = {
'foo': 'BAR'
}
return response
and this is the resulting JSON:
{
"meta": {
"total_count": 1
},
"items": [
{
"id": 8,
"meta": {
"type": "destination.DestinationPage",
"detail_url": "http://localhost/api/v2/page/8/",
"html_url": "http://localhost/my-page-title/",
"slug": "my-page-title",
"first_published_at": "2019-02-19T17:15:13.952708Z"
},
"title": "My page title"
}
],
"custom": {
"FOO": 'BAR'
}
}

Related

Firebase Cloud Firestore - Fail to write via REST API

This is not an authentication error, write is enabled on the database rules.
My cloud Firestore database looks like the picture below.
There is a COLLECTION called colA, inside it there is a DOCUMENT called docA, and inside it there are some fields (strings) stored.
On Postman, if I do GET https://firestore.googleapis.com/v1/projects/eletronica-ab6b1/databases/(default)/documents/colA/docA, I do receive the following answer, and it is correct:
{
"name": "projects/eletronica-ab6b1/databases/(default)/documents/colA/docA",
"fields": {
"fieldB": {
"stringValue": "ABCD"
},
"fieldA": {
"stringValue": "888"
}
},
"createTime": "2020-01-31T16:48:26.859181Z",
"updateTime": "2020-02-05T19:21:49.654340Z"
}
Now, when I try to write a new field (fieldC) via POST https://firestore.googleapis.com/v1/projects/eletronica-ab6b1/databases/(default)/documents/colA/docA, with JSON content:
{
"name": "projects/eletronica-ab6b1/databases/(default)/documents/colA/docA",
"fields": {
"fieldC": {
"stringValue": "1000"
}
}
}
After SEND, I receive this:
{
"error": {
"code": 400,
"message": "Document parent name \"projects/eletronica-ab6b1/databases/(default)/documents/colA\" lacks \"/\" at index 60.",
"status": "INVALID_ARGUMENT"
}
}
What I'm doing wrong? I really would like to write strings there via REST API.
Regards.
Updating a document is done with a PATCH request, according to the [reference documentation).
A POST request is used to create a new document in a collection, which probably explains the error you get: you're pointing to a document, but POST expects a collection path.

Wagtail getting all tags of a specific page type using api

I am trying to find a way to get the tag list of specific page types using wagtail api.
class BlogPage(FreeFormPage):
#...
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
class WorkPage(FreeFormPage):
#...
tags = ClusterTaggableManager(through=WorkPageTag, blank=True)
On the docs it is clear that there are endpoints for pages, documents and images. But I could not find an information on tags.
Do I have to create another specific page type model that collects all the tags and send them via pages endpoint?
Or do I have to add a custom endpoint for tag model?
You don't need to create a custom API endpoint at all. Instead, you'd tell Django Rest Framework what to return when it tries to serialize your model fields.
Below is an example of how to use a custom serializer with a ClusterTaggableManager (tags)
from modelcluster.contrib.taggit import ClusterTaggableManager
from rest_framework.fields import Field
from wagtail.core.models import Page
from wagtail.api import APIField
class CustomTagSerializer(Field):
"""Custom Tag Serializer."""
def to_representation(self, value):
"""Loop through all the tags and return the name, slug and caption as a Dict."""
return [
{
"name": tag.name,
"slug": tag.slug,
"pk": tag.pk
}
for tag in value.all()
]
class BlogPage(Page):
"""Custom Wagtail Page with Tags."""
# ... other fields
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
api_fields = [
APIField("title"),
# ... other fields to turn into JSON
APIField("tags", serializer=CustomTagSerializer()),
]
You'll use api_fields = [APIField(), APIField()...] and a serializer that takes a Field class. And in the to_representation() method you can loop through all the tags and return a list of dicts (I've written a list comprehension in the example above).
If you open your localhost:8000/api/v2/pages/{page_id} you'll see JSON that looks something very similar to this:
{
"id": 4,
"meta": {
"...": "...",
},
"title": "Blog Page",
"tags": [
{
"name": "Multiple worded tag",
"slug": "multiple-worded-tag",
"pk": 1
},
{
"name": "tag1",
"slug": "tag1",
"pk": 2
}
]
}

getconfig() for Community Connectors, how to employ user input

The Community Connector feature is very new, and I have searched, there isn't much information. We are building a Community Connector to enable Data Studio to pull API data from Google My Business Insights.
the getconfig() function is described here: https://developers.google.com/datastudio/connector/reference#getconfig
We can display our configuration options to the user, that was easy, but the API reference is unclear what the next step is: how to pass the user input to the next step. Pardon me if I am not using the proper terms here.
var config = {
configParams: [
{
"type": "SELECT_SINGLE",
"name": "SELECT_SINGLE",
"displayName": "Select a Location",
"helpText": "Pick One!",
"options": [
{
"label": locationName,
"value": name
},
{
"label": "altLocationName",
"value": "altName"
}
]
},
]
};
return config;
}
The preceding code displays properly to the user and the user can make a selection from the pull-down in Data Studio when making an initial data connection. But to repeat the question another way: how do we access the selection that the user chose?
The getData(), getSchema(), and getConfig() functions are all called with a parameter (which is called "request" in the documentation). The parameter is an object containing various info at each stage.
At the getConfig() stage, it includes a property called languageCode, in my case set to 'en-GB'.
The getSchema() stage is provided a property called configParams, which is essentially the result of all the settings in getConfig() after the user has set them.
Finally, getData() gets the most info, including whether this request is for extracting sample data for google to run heuristics on, and most importantly: again the configParams.
Here's what a sample request object might look like:
{ //------ Present in:
languageCode: en-GB, //////-Only getConfig()
configParams: { //////-getSchema() + getData()
SELECT_SINGLE: altName ////-+
}, //
scriptParams: { //////-Only getData()
sampleExtraction: true ////-|
lastRefresh: 'new Date()' ////-+
}, //
fields: [ //////-Only getData()
{ name: FooAwesomeness }, ////-|
{ name: BarMagicality }, ////-|
{ name: BazPizzazz } ////-+
] //
dimensionsFilters: [ //////-Only getData()
[{ // |
fieldName: "string", ////-|
values: ["string", ...], ////-|
type: DimensionsFilterType, ////-|
operator: Operator ////-+
}] //
] //
} //------
Do note
that the name field in your code, currently set to SELECT_SINGLE, would be better suited to be called location because that it how you'll access it later on.
In this way you would
access:
request.configParams.location
rather than
request.configParams.SELECT_SINGLE
:)
Also note
that the format for specifying a configuration screen has been updated. Your configuration would now be able to be done as follows:
function getConfig(request) {
var cc = DataStudioApp.createCommunityConnector();
var config = cc.getConfig();
config
.newSelectSingle()
.setId('location') // You can call this "location"
.setName('Select a Location')
.setHelpText('Pick One!')
.addOption(config.newOptionBuilder()
.setLabel('Location Name')
.setValue('value'))
.addOption(config.newOptionBuilder()
.setLabel('Alternate Location Name')
.setValue('altValue'))
config.setDateRangeRequired(true);
config.setIsSteppedConfig(false);
return config.build();
}
See: Connector API Reference
See: Build a Connector Guide
The user selections will be passed to getSchema() and getData() requests under configParams object.
Using your example, let's assume the user selects altLocationName in the configuration screen. In your getSchema() and getData() functions, request.configParams.SELECT_SINGLE should return altName.

Best practice Backand regarding JSON transformation and related objects

I am currently trying to figure out how to setup my Backand app and its REST API. This question is related to question: Backand deep querying. However, I was hoping that I could get some best practice code examples on how to perform server side code to perform a loop and create a JSON responds with the following criteria:
I want to be able to make a REST request to Backand and get one data object back that has manipulated/merged two data objects from my database.
I have an object called "media" and another named "users". Obviously, users contain user information and media contains information on a picture that the user has uploaded. The two objects are related by the userId and by collection set in Backand. I want to make a GET request that responds with a JSON object with all pictures and a nested user object on each picture object that contains the related user information. I know that I get back "relatedObjects", and I could then make some manipulation on the client side, but I am hoping that there is another easier way to do this from the Backand administration system either on server side code or as a query.
So, my question is, what's the best way to produce a REST call that responds a database object with nested related data object through Backand?
Here's the object models (shorten for clarity):
User object model as set up in Backand
{
"name": "users",
"fields": {
"media": {
"collection": "media",
"via": "user"
},
"email": {
"type": "string"
},
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
}
} }
Media object model as set up in Backand
{
"name": "media",
"fields": {
"description": {
"type": "string"
},
"thumbnail": {
"type": "string"
},
"fullImage": {
"type": "string"
},
"user": {
"object": "users"
}
}}
Final JSON response that I am looking for:
{
description: 'Blah',
thumbnail: 'someImageUrl.jpg',
fullImage: 'someImageUrl.jpg',
user: {
firstName: 'John'
lastName: 'Smith'
email: 'john#smith.com'
}
}
Just in case anybody else comes across this, I chose to do it with server-side javascript code, since my backend, SQL and NoSQL query skills are very weak. I'm guessing a noSQL query would probably be better in terms of performance. And I would still like to see how it could be done in noSQL. Anyway my server-side javascript code in a Backand action does the job. Here it is:
/* globals
$http - Service for AJAX calls
CONSTS - CONSTS.apiUrl for Backands API URL
Config - Global Configuration
socket - Send realtime database communication
files - file handler, performs upload and delete of files
request - the current http request
*/
'use strict';
function backandCallback(userInput, dbRow, parameters, userProfile) {
var response = [];
var request =
$http({
method: "GET",
url: CONSTS.apiUrl + "/1/objects/media",
headers: {"Authorization": userProfile.token},
params: {
exclude: 'metadata',
deep: true
}
});
var object = request.data;
var related = request.relatedObjects.users;
for (media in object) {
if (object.hasOwnProperty(media)) {
for (user in related) {
if (object[media].user == related[user].id) {
response.push({
id: object[media].id,
thumbnailUrl: object[media].thumbnail,
description: object[media].description,
fullName: related[user].firstName + ' ' + related[user].lastName,
email: related[user].email
});
}
}
}
}
return response;
}

Fetching Facebook Timeline posts using Facebook API

I'm facing following issues while fetching facebook user/pages/group timeline posts:
I'm not getting all information (photos urls, link, created_time, etc) in posts objects retrieved using this. Following is a sample response of this api:
[
{
"message": "this is going to be real fun https://localhost.com/N1AyEvZp",
"story": "Rajveer Singh added photos to XYZ Photos in My-group.",
"updated_time": "2015-09-03T16:27:34+0000",
"id": "405944472923733_413853035466210"
},
{
"message": "this is going to be fun https://localhost.com/EJo1WvZp",
"story": "Rajveer Singh added photos to XYZ Photos in My-group.",
"updated_time": "2015-09-03T16:14:41+0000",
"id": "405944472923733_413848848799962"
},
{
"message": "this is going to be some real funhttps://localhost.com/VyVKdWga",
"story": "Rajveer Singh added photos to XYZ Photos in My-group.",
"updated_time": "2015-09-02T15:45:08+0000",
"id": "405944472923733_413582785493235"
}
]
This response is missing the photo urls, links, captions, etc from the posts. Is there any different api for fetching those informations ? Also, if I directly hit the one of the post object /405944472923733_413582785493235 then I get following response:
{
"created_time": "2015-09-02T15:45:07+0000",
"message": "this is going to be some real funhttps://localhost.com/VyVKdWga",
"story": "Rajveer Singh added photos to XYZ Photos in My-group.",
"id": "405944472923733_413582785493235"
}
Though I get created_time in this response but pictures, urls, are still missing. I found this api deprecated. Is there any different api which can give me all the info ?
The above response is also missing comments and replies. On google search I found that we can get comments using /405944472923733_413582785493235/comments api but again that api doesn't mention the exact comments count. Also, the api doesn't give all the comments in a single api call. They have a pagination kind of thing. Can anyone tell me how can I get exact count of comments, replies to comments, and retrieve all the comments in a single api call. If we can't retrieve all the comments in a single go, then how can we use pagination ? I need to send all the comments related to a post to my front-end. with pagination, how can I achieve that ? Do I need to store the previous/next urls somewhere in front-end ? Following is a sample response of this:
{
"data": [
{
"id": "[post_id]",
"from": {
"name": "[name]",
"id": "[id]"
},
"message": "[message]",
"created_time": "2011-01-23T02:36:23+0000"
},
{
"id": "[id]",
"from": {
"name": "[name]",
"id": "[id]"
},
"message": "[message]",
"created_time": "2011-01-23T05:16:56+0000"
}
],
"paging": {
"cursors": {
"after": "WTI5dGJXVnVkRjlqZFhKemIzSTZOREUzTVRJeE5qWTRORGN5Tmpnd09qRTBOREl3T0RRd09URT0=",
"before": "WTI5dGJXVnVkRjlqZFhKemIzSTZOREUzTVRFNU16RTRORGN5T1RFMU9qRTBOREl3T0RRd05qZz0="
},
"previous": "previousUrl",
"next": "nextUrl"
}
How to get count of all the likes and shares of a post ? I simply want the count and doesn't want who actually likes/shared the post. How can I get that ? I found that using /likes gives a list of all those who liked the post but it doesn't give the count. Following is a sampple response of that:
{
"data": [
{
"id": "824565440992306"
}
],
"paging": {
"cursors": {
"after": "ODI0NTY1NDQwOTkyMzA2",
"before": "ODI0NTY1NDQwOTkyMzA2"
}
}
}
General Information:
I'm using Node.js Javascript SDK for hitting FB APIs.
I'm using correct access token, so that's not an issue for sure.
I have gone through this and this but didn't get any help from them.
I need all the information related to wall posts on my back-end so that I can send all data to my front-end for proper rendering. This is a screenshot of my front-end and all the information which I need in front-end.
Can anyone please try to clear my doubts ?
Also, if there is any optimized way of fetching all this information, then please do suggest. Your suggestions are welcome.

Resources