Ich frage mich, ob es möglich ist, DataProvider / Resource / List so zu konfigurieren, dass REST-URLs wie api/users/1/roles unterstützt werden.

Für die RESTful-API ist es ein sehr häufiger Anwendungsfall, untergeordnete Elemente einer bestimmten übergeordneten Entität abzurufen, aber ich kann mir nicht vorstellen, wie React Admin eingerichtet und dies erreicht werden soll. Ich verwende einen benutzerdefinierten DataProvider, der auf dem OData-Spezifikations-Backend basiert.

Ich verstehe, dass ich Rollen eines bestimmten Benutzers durch gefilterte Anforderung auf api/roles?filter={userId: 1} oder ähnlichem erhalten kann, aber mein Problem ist, dass meine Benutzer und Rollen in einer Viele-zu-Viele-Beziehung stehen, sodass Beziehungsreferenzen in der Pivot-Tabelle gespeichert werden. Mit anderen Worten, ich habe keinen Verweis auf den Benutzer in der Rollentabelle, daher kann ich sie nicht filtern.

Beaufsichtige ich etwas oder gibt es einen Ansatz, den ich einfach nicht sehe?

BEARBEITEN: Die REST-API ist in der OData-Spezifikation integriert und unterstützt viele-zu-viele-Beziehungen mit der klassischen Pivot- (oder Zwischen-) Tabelle. Diese Tabelle ist in der API nicht verfügbar, wird jedoch in URLs wie der oben genannten verwendet. Daher kann ich nicht direkt als Ressource darauf zugreifen.

Das Schema für Benutzer-Rollen-Beziehungen sieht ebenfalls ziemlich normal aus.

|----------|    |-----------|     |--------|
| USER     |    | User_Role |     | Role   |
|----------|    |-----------|     |--------|
| Id       |-\  | Id        |   /-| Id     |
| Login    |  \-| UserId    |  /  | Name   |
| Password |    | RoleId    |-/   | Code   |
|----------|    |-----------|     |--------|
5
Allwe 18 Jän. 2019 im 16:30

3 Antworten

Beste Antwort

TL; DR: Standardmäßig unterstützt React Admin keine verschachtelten Ressourcen. Sie müssen schreibe einen benutzerdefinierten Datenprovider.

Diese Frage wurde in einer früheren Ausgabe beantwortet: maremelab / react-admin # 261

Detaillierte Antwort

Der Standarddatenanbieter in React Admin ist {{X0 }}.

Wie in der Dokumentation erläutert, unterstützt diese Bibliothek keine verschachtelten Ressourcen, da sie nur den Ressourcennamen und die Ressourcen-ID zum Erstellen einer Ressourcen-URL verwendet:

Simple REST Data Provider

Um verschachtelte Ressourcen zu unterstützen, müssen Sie Ihren eigenen Datenprovider schreiben.

Die Unterstützung für verschachtelte Ressourcen erfolgt wiederholt Funktion Anfrage aber zu diesem Zeitpunkt möchte das Kernteam diese Arbeitsbelastung nicht bewältigen.

Ich empfehle dringend, Ihre Kräfte zu sammeln, einen externen Datenprovider zu schreiben und ihn wie einen ra-data-odata Provider zu veröffentlichen. Es wäre eine großartige Ergänzung und wir werden uns freuen, Ihnen bei diesem externen Paket helfen zu können.

5
Kmaschta 21 Jän. 2019 im 15:04

Ihre Frage wurde bereits hier beantwortet, aber ich möchte Sie über meine Problemumgehung informieren, damit React-Admin mit vielen zusammenarbeiten kann -zu-vielen Beziehungen.

Wie in der genannten Antwort erwähnt, müssen Sie den DataProvider erweitern, damit er Ressourcen mit einer Viele-zu-Viele-Beziehung abruft. Sie müssen jedoch das neue REST-Verb verwenden. Nehmen wir an, GET_MANY_MANY_REFERENCE befindet sich irgendwo in Ihrer Anwendung. Da verschiedene REST-Services / APIs unterschiedliche Routenformate zum Abrufen verwandter Ressourcen haben können, habe ich nicht versucht, einen neuen DataProvider zu erstellen. Ich weiß, dass dies keine gute Lösung ist, aber für kurze Fristen ist es sehr einfach.

Meine Lösung bestand darin, sich von <ReferenceManyField> inspirieren zu lassen und eine neue Komponente <ReferenceManyManyField> für viele-zu-viele-Beziehungen zu erstellen. Diese Komponente ruft verwandte Datensätze auf componentDidMount mithilfe der Abruf-API . Bei der Antwort werden die Antwortdaten verwendet, um Objekte zu erstellen, wobei ein Objekt ein Objekt mit Schlüsseln als Datensatz-IDs ist und das jeweilige Datensatzobjekt sowie ein IDs-Array mit den IDs von Datensätzen bewertet werden. Dies wird zusammen mit anderen Statusvariablen wie page, sort, perPage, total an untergeordnete Elemente übergeben, um die Paginierung und Reihenfolge der Daten zu handhaben. Beachten Sie, dass das Ändern der Reihenfolge der Daten in einem Datagrid bedeutet, dass eine neue Anforderung an die API gestellt wird. Diese Komponente ist in einen Controller und eine Ansicht wie <ReferencemanyField> unterteilt, in der der Controller Daten abruft, verwaltet und an untergeordnete Elemente weitergibt. Die Ansicht, die Controllerdaten empfängt und an untergeordnete Elemente weitergibt, rendert deren Inhalt. Dadurch konnte ich viele-zu-viele-Beziehungsdaten auf einem Datagrid rendern, auch wenn dies mit einigen Einschränkungen eine Komponente ist, die zu meinem Projekt aggregiert werden kann, und nur dann mit meiner aktuellen API arbeiten, wenn sich etwas ändert, in das ich das Feld ändern müsste. aber im Moment funktioniert es und kann entlang meiner App wiederverwendet werden.

Die Implementierungsdetails lauten wie folgt:

//ReferenceManyManyField
export const ReferenceManyManyField = ({children, ...prop}) => {
  if(React.Children.count(children) !== 1) {
    throw new Error( '<ReferenceManyField> only accepts a single child (like <Datagrid>)' )
  }

  return <ReferenceManyManyFieldController {...props}>
    {controllerProps => (<ReferenceManyManyFieldView 
    {...props} 
    {...{children, ...controllerProps}} /> )}
  </ReferenceManyManyFieldController>

//ReferenceManyManyFieldController
class ReferenceManyManyFieldController extends Component {

  constructor(props){
    super(props)
    //State to manage sorting and pagination, <ReferecemanyField> uses some props from react-redux 
    //I discarded react-redux for simplicity/control however in the final solution react-redux might be incorporated
    this.state = {
      sort: props.sort,
      page: 1,
      perPage: props.perPage,
      total: 0
    }
  }

  componentWillMount() {
    this.fetchRelated()
  }

  //This could be a call to your custom dataProvider with a new REST verb
  fetchRelated({ record, resource, reference, showNotification, fetchStart, fetchEnd } = this.props){
    //fetchStart and fetchEnd are methods that signal an operation is being made and make active/deactivate loading indicator, dataProvider or sagas should do this
    fetchStart()
    dataProvider(GET_LIST,`${resource}/${record.id}/${reference}`,{
      sort: this.state.sort,
      pagination: {
        page: this.state.page,
        perPage: this.state.perPage
      }
    })
    .then(response => {
      const ids = []
      const data = response.data.reduce((acc, record) => {
        ids.push(record.id)
        return {...acc, [record.id]: record}
      }, {})
      this.setState({data, ids, total:response.total})
    })
    .catch(e => {
      console.error(e)
      showNotification('ra.notification.http_error')
    })
    .finally(fetchEnd)
  }

  //Set methods are here to manage pagination and ordering,
  //again <ReferenceManyField> uses react-redux to manage this
  setSort = field => {
    const order =
        this.state.sort.field === field &&
        this.state.sort.order === 'ASC'
            ? 'DESC'
            : 'ASC';
    this.setState({ sort: { field, order } }, this.fetchRelated);
  };

  setPage = page => this.setState({ page }, this.fetchRelated);

  setPerPage = perPage => this.setState({ perPage }, this.fetchRelated);

  render(){
    const { resource, reference, children, basePath } = this.props
    const { page, perPage, total } = this.state;

    //Changed basePath to be reference name so in children can nest other resources, not sure why the use of replace, maybe to maintain plurals, don't remember 
    const referenceBasePath = basePath.replace(resource, reference);

    return children({
      currentSort: this.state.sort,
      data: this.state.data,
      ids: this.state.ids,
      isLoading: typeof this.state.ids === 'undefined',
      page,
      perPage,
      referenceBasePath,
      setPage: this.setPage,
      setPerPage: this.setPerPage,
      setSort: this.setSort,
      total
    })
  }

}

ReferenceManyManyFieldController.defaultProps = {
  perPage: 25,
  sort: {field: 'id', order: 'DESC'}
}

//ReferenceManyManyFieldView
export const ReferenceManyManyFieldView = ({
  children,
  classes = {},
  className,
  currentSort,
  data,
  ids,
  isLoading,
  page,
  pagination,
  perPage,
  reference,
  referenceBasePath,
  setPerPage,
  setPage,
  setSort,
  total
}) => (
  isLoading ? 
    <LinearProgress className={classes.progress} />
  :
      <Fragment>
        {React.cloneElement(children, {
          className,
          resource: reference,
          ids,
          data,
          basePath: referenceBasePath,
          currentSort,
          setSort,
          total
        })}
        {pagination && React.cloneElement(pagination, {
          page,
          perPage,
          setPage,
          setPerPage,
          total
        })}
      </Fragment>
);

//Assuming the question example, the presentation of many-to-many relationship would be something like
const UserShow = ({...props}) => (
  <Show {...props}>
    <TabbedShowLayout>
      <Tab label='User Roles'>
        <ReferenceManyManyField source='users' reference='roles' addLabel={false} pagination={<Pagination/>}>
          <Datagrid>
            <TextField source='name'/>
            <TextField source='code'/>
          </Datagrid>
        </ReferenceManyManyField>
      </Tab>
    </TabbedShowLayout>
  </Show>
)
//Used <TabbedShowLayout> because is what I use in my project, not sure if works under <Show> or <SimpleShowLayout>, but I think it work since I use it in other contexts

Ich denke, die Implementierung kann verbessert werden und ist besser mit React-Admin kompatibel. In anderen Referenzfeldern wird der Datenabruf im React-Redux-Status gespeichert, in dieser Implementierung nicht. Die Beziehung wird nirgendwo außer der Komponente gespeichert, sodass die Anwendung nicht offline funktioniert, da keine Daten abgerufen werden können und nicht einmal eine Bestellung möglich ist.

4
Mário Ferreiro 22 Jän. 2019 im 18:11

Hatte eine sehr ähnliche Frage. Meine Lösung war eher ein Hack, aber etwas einfacher zu implementieren, wenn Sie möchten lediglich ein ReferenceManyField aktivieren. Nur das dataProvider muss geändert werden:

Ich wiederhole meine hier modifizierte Lösung für die aktuelle Frage:

Verwenden der Aktie ReferenceManyField:

<Show {...props}>
    <TabbedShowLayout>
        <Tab label="Roles">
            <ReferenceManyField reference="roles" target="_nested_users_id" pagination={<Pagination/>} >
                <Datagrid>
                    <TextField source="role" />
                </Datagrid>
            </ReferenceManyField>
        </Tab>
    </TabbedShowLayout>
</Show>

Anschließend habe ich meinen dataProvider geändert, bei dem es sich um eine Abzweigung von ra-jsonapi-client handelt. Ich habe index.js unter case GET_MANY_REFERENCE geändert:

      // Add the reference id to the filter params.
      query[`filter[${params.target}]`] = params.id;

      url = `${apiUrl}/${resource}?${stringify(query)}`;

Dazu:

      // Add the reference id to the filter params.
      let refResource;
      const match = /_nested_(.*)_id/g.exec(params.target);
      if (match != null) {
        refResource = `${match[1]}/${params.id}/${resource}`;
      } else {
        query[`filter[${params.target}]`] = params.id;
        refResource = resource;
      }

      url = `${apiUrl}/${refResource}?${stringify(query)}`;

Im Grunde ordne ich die Parameter einfach der URL für den Sonderfall zu, in dem target mit einem fest codierten regulären Ausdruck übereinstimmt.

ReferenceManyField hätte normalerweise dazu geführt, dass der dataProvider api/roles?filter[_nested_users_id]=1 aufruft, und durch diese Änderung wird stattdessen der dataProvider api/users/1/roles aufgerufen. Es ist transparent zu reagieren-admin.

Nicht elegant, aber es funktioniert und scheint nichts am Frontend zu beschädigen.

0
vchev005 6 Juli 2019 im 02:29