-
-
Save thclark/722ea2bae42db05c38a5c583ba976c52 to your computer and use it in GitHub Desktop.
import { stringify } from 'query-string' | |
import { fetchUtils } from 'react-admin' | |
import Cookies from 'universal-cookie' | |
import { store } from 'index' | |
const camelCaseKeys = require('camelcase-keys') | |
const snakeCaseKeys = require('snakecase-keys') | |
const cookies = new Cookies() | |
const fetchJson = (url, options = {}) => { | |
const fullUrl = `${window.baseUrl}/api/${url}` | |
const { access } = store.getState().auth | |
const cookie = cookies.get('csrftoken') | |
// Convert keys to snake case (to comply with backend spec) and serialize body to json | |
if (options.body) { | |
// eslint-disable-next-line no-param-reassign | |
options.body = JSON.stringify(snakeCaseKeys(options.body, { deep: true })) | |
} | |
if (!options.headers) { | |
// eslint-disable-next-line no-param-reassign | |
options.headers = new Headers({ Accept: 'application/json' }) | |
} | |
// Inject the Authorization header from the redux store, if there is one | |
if (access) { | |
options.headers.set('Authorization', `JWT ${access.token}`) | |
} | |
// Inject the CSRF token, if there is one | |
if (cookie) { | |
options.headers.set('X-CSRFToken', `${cookie}`) | |
} | |
// add your own headers here | |
// options.headers.set('X-Custom-Header', 'foobar') | |
return fetchUtils.fetchJson(fullUrl, options) | |
} | |
const getListFromResponse = response => { | |
const { headers, json } = response | |
if ('count' in json) { | |
return { data: camelCaseKeys(json.results, { deep: true }), total: json.count } | |
} | |
if (headers.has('content-range')) { | |
return { | |
data: json, | |
total: parseInt( | |
headers | |
.get('content-range') | |
.split('/') | |
.pop(), | |
10, | |
), | |
} | |
} | |
if ('detail' in json && json.detail === 'Invalid page.') { | |
return { data: [], total: 0 } | |
} | |
throw new Error('The total number of results is unknown. The DRF data provider expects responses for lists of resources to contain this information to build the pagination. If you\'re not using the default PageNumberPagination class, please include this information using the Content-Range header OR a "count" key inside the response.') | |
} | |
/** | |
* Maps react-admin queries to the default format of Django REST Framework | |
*/ | |
export default { | |
getList: (resource, params) => { | |
const options = {} | |
const { page, perPage } = params.pagination | |
const { field, order } = params.sort | |
const { filter } = params | |
const query = { | |
page, | |
page_size: perPage, | |
ordering: `${order === 'ASC' ? '' : '-'}${field}`, | |
...filter, | |
} | |
const url = `${resource}/?${stringify(query)}` | |
return fetchJson(url, options).then(response => getListFromResponse(response)) | |
}, | |
getOne: (resource, params) => { | |
const options = {} | |
const url = `${resource}/${params.id}/` | |
return fetchJson(url, options).then(response => { | |
return { data: camelCaseKeys(response.json, { deep: true }) } | |
}) | |
}, | |
getMany: (resource, params) => { | |
const options = { method: 'GET' } | |
return Promise.all( | |
params.ids.map(id => fetchJson(`${resource}/${id}/`, options)), | |
).then(responses => ({ | |
data: responses.map(response => camelCaseKeys(response.json, { deep: true })), | |
})) | |
}, | |
getManyReference: (resource, params) => { | |
const { page, perPage } = params.pagination | |
const { field, order } = params.sort | |
const { filter, target, id } = params | |
const query = { | |
page, | |
page_size: perPage, | |
ordering: `${order === 'ASC' ? '' : '-'}${field}`, | |
...filter, | |
[target]: id, | |
} | |
const url = `${resource}/?${stringify(query)}` | |
const options = {} | |
return fetchJson(url, options).then(response => getListFromResponse(response)) | |
}, | |
create: (resource, params) => { | |
const url = `${resource}/` | |
const options = { | |
method: 'POST', | |
body: params.data, | |
} | |
return fetchJson(url, options).then(response => { | |
// TODO review whether we need to update all data or just the ID | |
// return { data: camelCaseKeys(response.json, { deep: true }) } | |
return { data: { ...params.data, id: response.json.id } } | |
}) | |
}, | |
update: (resource, params) => { | |
const url = `${resource}/${params.id}/` | |
const options = { | |
method: 'PUT', | |
body: params.data, | |
} | |
return fetchJson(url, options).then(response => { | |
return { data: camelCaseKeys(response.json, { deep: true }) } | |
}) | |
}, | |
updateMany: (resource, params) => { | |
return Promise.all( | |
params.ids.map(id => fetchJson(`${resource}/${id}`, { | |
method: 'PUT', | |
body: params.data, | |
})), | |
).then(responses => ({ | |
data: responses.map(response => camelCaseKeys(response.json, { deep: true })), | |
})) | |
}, | |
delete: (resource, params) => { | |
const url = `${resource}/${params.id}/` | |
const options = { | |
method: 'DELETE', | |
} | |
return fetchJson(url, options).then(() => { | |
// todo should this really be like this? or like the default | |
// return { data: response.json } | |
return { data: params.previousData } | |
}) | |
}, | |
deleteMany: (resource, params) => { | |
// TODO can we check whether the viewsets need this customisation? | |
// Perhaps we can make a single query like the example in | |
// https://github.com/marmelab/react-admin/blob/v3.0.0-beta.0/docs/DataProviders.md | |
return Promise.all( | |
params.ids.map(id => fetchJson(`${resource}/${id}`, { method: 'DELETE' })), | |
).then(responses => ({ | |
data: responses.map(response => camelCaseKeys(response.json, { deep: true })), | |
})) | |
}, | |
} |
Hi @l0n3woof I'm afraid I can't. I tried out react-admin for a while a few months back (see this issue), but found it too tightly coupled to work with so am not supporting this. I just put this up because I'd put quite a bit of work in and hoped it'd help someone else. Over to you to add the examples!
Although @l0n3woof if it helps, the baseUrl is just the URL the api is at. In local development, that might be something like 'http://localhost:8000' or in production 'https://api.yourdomain.com'
thanks
@nadeeraka did you figure it out? I have implemented it in my project and I'm getting data back from the API, however the data is not being displayed in my list component plus I get the following error .... "The total number of results is unknown. The DRF data provider expects responses for lists of resources to contain this information"
Can you please provide some examples of how to incorporate your provider in a react app?
Right now what I did is made one file djangodataprovider.js and imported it in my react-admin App.tsx. But I am unable to figure out how to provide base url to the gist you have given above.