drf-react-by-schema 0.28.25 → 0.28.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/@types/index.d.ts +115 -6
- package/dist/@types/index.d.ts.map +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +2 -1
- package/dist/components/details/DetailBySchema.d.ts.map +1 -1
- package/dist/components/details/DetailBySchema.js +152 -29
- package/dist/components/details/DetailFieldBySchema.d.ts +2 -2
- package/dist/components/details/DetailFieldBySchema.d.ts.map +1 -1
- package/dist/components/details/DetailFieldBySchema.js +49 -26
- package/dist/components/details/DetailPage.d.ts +8 -0
- package/dist/components/details/DetailPage.d.ts.map +1 -0
- package/dist/components/details/DetailPage.js +123 -0
- package/dist/components/details/detailPaths.d.ts +8 -0
- package/dist/components/details/detailPaths.d.ts.map +1 -0
- package/dist/components/details/detailPaths.js +41 -0
- package/dist/components/forms/Form.d.ts +2 -0
- package/dist/components/forms/Form.d.ts.map +1 -1
- package/dist/hooks/useDetailData.d.ts +7 -0
- package/dist/hooks/useDetailData.d.ts.map +1 -0
- package/dist/hooks/useDetailData.js +70 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/docs/API_REFERENCE.md +665 -0
- package/docs/FIELD_TYPES.md +145 -0
- package/docs/localization.md +173 -0
- package/docs/password-reset-and-email.md +145 -0
- package/package.json +3 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Field types & widgets
|
|
2
|
+
|
|
3
|
+
`drf-react-by-schema` derives every form widget, validation rule and DataGrid column
|
|
4
|
+
directly from the schema generated by `drf-react-by-schema-django` (via the model
|
|
5
|
+
serializers + `OPTIONS`/`sync_react`). This page documents each schema field `type`
|
|
6
|
+
and the matching widget it renders.
|
|
7
|
+
|
|
8
|
+
## Quick reference
|
|
9
|
+
|
|
10
|
+
The `type` value comes from the schema that Django exposes for each model field.
|
|
11
|
+
The table below maps every type to its **form widget** and its **DataGrid** behavior.
|
|
12
|
+
|
|
13
|
+
| Schema type | Form widget | DataGrid read (cell) | DataGrid edit | Notes |
|
|
14
|
+
| --- | --- | --- | --- | --- |
|
|
15
|
+
| `string` | `TextField` (single line) | text | text input (mask if `pattern_format`) | |
|
|
16
|
+
| `string` + `model_html` | **Tiptap WYSIWYG** (`HtmlFieldBySchema`) | sanitized **HTML** | text input | optional peers; falls back to multiline `TextField` |
|
|
17
|
+
| `email` | `TextField` (email) | text | text input | |
|
|
18
|
+
| `password` | `TextField` (`type="password"`) | text (hidden) | text input | |
|
|
19
|
+
| `number`, `integer` | `TextField` (numeric) | number (pt-BR formatted) | numeric input | `min`/`max` from validators |
|
|
20
|
+
| `decimal`, `float` | `FloatFieldBySchema` (decimal input) | number (pt-BR, `decimal_places`) | `GridDecimalInput` | honors `decimal_places`, `is_currency` |
|
|
21
|
+
| `date` | `DesktopDatePickerBySchema` (MUI date picker) | date (`date_views` format) | `GridDateInput` | |
|
|
22
|
+
| `datetime` | `DesktopDateTimePickerBySchema` (MUI date-time picker) | date-time (`DD/MM/YYYY HH:mm`) | `GridDateTimeInput` | |
|
|
23
|
+
| `boolean` | `BooleanFieldBySchema` (switch) | `Sim` / `Não` | `BooleanInputCell` | |
|
|
24
|
+
| `choice` | `AutocompleteFieldBySchema` (single select) | label (join) | `SelectEditInputCell` | `choices` provide options |
|
|
25
|
+
| `list` / `multiple choice` | `AutocompleteFieldBySchema` (multi-select) | label (comma-joined) | `SelectEditInputCell` (`multiple`) | `many: true`; child `choices` |
|
|
26
|
+
| `field` / `nested object` | `EditableAutocompleteFieldBySchema` (related) | related label | `SelectEditInputCell` | `related_editable` enables editing via dialog |
|
|
27
|
+
| `file` / `file upload` | `FileFieldBySchema` (upload) | file name | `FileInputCell` | `allowed_mime_types` |
|
|
28
|
+
| `image` / `image upload` | `FileFieldBySchema` (image upload) | thumbnail preview | `FileInputCell` | `allowed_mime_types` |
|
|
29
|
+
| `array` | `TextField` (default fallback) | text | text input | |
|
|
30
|
+
| `slug` | `TextField` | text | text input | forced `read_only` |
|
|
31
|
+
| `point` | Leaflet **point picker** (see Geometry below) | **thumb map** | **modal** point picker | requires PostGIS + leaflet |
|
|
32
|
+
| `polygon` | Leaflet **polygon draw** (see Geometry below) | **thumb map** | **modal** polygon draw | requires PostGIS + leaflet |
|
|
33
|
+
| `multipoint` | Leaflet **multi-point picker** (see Geometry below) | **thumb map** | **modal** multi-point picker | requires PostGIS + leaflet |
|
|
34
|
+
| `multipolygon` | Leaflet **multi-polygon draw** (see Geometry below) | **thumb map** | **modal** multi-polygon draw | requires PostGIS + leaflet |
|
|
35
|
+
| `geometrycollection` | Leaflet **points + polygons draw** (see Geometry below) | **thumb map** | **modal** geometry draw | requires PostGIS + leaflet |
|
|
36
|
+
|
|
37
|
+
## Rich text fields (`model_html`)
|
|
38
|
+
|
|
39
|
+
A Django `TextField(rich_text=True)` (from `drf_react_by_schema.fields`) keeps its plain
|
|
40
|
+
`string` type but adds `model_html: true` to the schema. The value **is HTML** — the same
|
|
41
|
+
format the Django admin's Summernote widgets store — so no conversion or migration is
|
|
42
|
+
needed for existing admin-authored content.
|
|
43
|
+
|
|
44
|
+
### Requirements
|
|
45
|
+
|
|
46
|
+
The editor is optional, like the map widgets. Install Tiptap in the **consuming app**:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install @tiptap/react @tiptap/starter-kit
|
|
50
|
+
# optional: sanitizes rich text in detail views
|
|
51
|
+
npm install dompurify
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Without Tiptap the field falls back to the multiline text field (raw HTML editable as
|
|
55
|
+
text), and the form keeps working. Without `dompurify` the detail view renders the value
|
|
56
|
+
as plain text (tags stripped) instead of injecting HTML.
|
|
57
|
+
|
|
58
|
+
### Widgets and behaviour
|
|
59
|
+
|
|
60
|
+
- **Form** (`HtmlFieldBySchema` → `RichTextEditorInput`): a Tiptap editor with an MUI
|
|
61
|
+
toolbar — paragraph/H1–H3, bold, italic, underline, strikethrough, bullet and ordered
|
|
62
|
+
lists, quote, code block, horizontal rule, link (dialog), clear formatting, undo/redo.
|
|
63
|
+
The form value is the editor's HTML (`getHTML()`), empty content is saved as `''`.
|
|
64
|
+
- **Detail** (`DetailFieldBySchema` → `HtmlContent`): the HTML is sanitized with
|
|
65
|
+
DOMPurify against an allow-list of tags/attributes, then rendered read-only with the
|
|
66
|
+
package's typography scale.
|
|
67
|
+
- **Grid**: the cell shows the stored HTML as text (no rendering); the toolbar strings are
|
|
68
|
+
localized through `theme.texts.richText` (see `docs/localization.md`).
|
|
69
|
+
|
|
70
|
+
Sanitization happens on **render**, not on save: the API keeps the HTML verbatim, so any
|
|
71
|
+
other client of the same column must sanitize it too.
|
|
72
|
+
|
|
73
|
+
## Geometry fields (PostGIS)
|
|
74
|
+
|
|
75
|
+
Geometry field types map to Django's GeoDjango model fields (`PointField`,
|
|
76
|
+
`PolygonField`, `MultiPointField`, `MultiPolygonField`, `GeometryCollectionField`)
|
|
77
|
+
serialized as **GeoJSON**. They are only emitted when the database engine is PostGIS.
|
|
78
|
+
|
|
79
|
+
### Requirements
|
|
80
|
+
|
|
81
|
+
The map widgets are optional. They require the following packages to be installed in
|
|
82
|
+
the **consuming app** (they are declared as *optional* peer dependencies of
|
|
83
|
+
`drf-react-by-schema`):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm install leaflet react-leaflet leaflet-draw
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
If any of them is missing, the field falls back to a plain widget so the app keeps
|
|
90
|
+
working:
|
|
91
|
+
|
|
92
|
+
- **`point`** → a single-line text input.
|
|
93
|
+
- **`polygon`, `multipoint`, `multipolygon`, `geometrycollection`** → a multiline text area.
|
|
94
|
+
|
|
95
|
+
In both cases the value stays a GeoJSON string.
|
|
96
|
+
|
|
97
|
+
### Widgets and behaviour
|
|
98
|
+
|
|
99
|
+
All geometry widgets share the same map + text editing UI:
|
|
100
|
+
|
|
101
|
+
- The **map** shows and edits the geometry over an OpenStreetMap base layer.
|
|
102
|
+
- A **text field** below the map is **always synchronized in both directions**:
|
|
103
|
+
- paste/edit a GeoJSON string → it renders immediately on the map;
|
|
104
|
+
- draw/move on the map → the text field updates to the generated GeoJSON.
|
|
105
|
+
- **Malformed (or wrong-type) GeoJSON** text is flagged as an **input error** — the
|
|
106
|
+
message shows `Dados malformados, corrija o GeoJSON para refletir no mapa.` and the
|
|
107
|
+
previously drawn features are kept on the map (they are not wiped).
|
|
108
|
+
- The read-only **grid cell** renders a **thumb map**: a static mini-map fitted to the
|
|
109
|
+
geometry with padding + a zoom cap, so the shape is shown with some surrounding
|
|
110
|
+
context.
|
|
111
|
+
- The **grid edit cell** is a button that opens a **modal** with the picker/drawer + the
|
|
112
|
+
synced GeoJSON text field.
|
|
113
|
+
|
|
114
|
+
Behaviour per type:
|
|
115
|
+
|
|
116
|
+
| `type` | Can add | Move existing | Remove existing | Single instance? |
|
|
117
|
+
| --- | --- | --- | --- | --- |
|
|
118
|
+
| `point` | add **one** marker | drag marker | via grid edit delete | **yes** |
|
|
119
|
+
| `polygon` | draw **one** polygon | draw-edit (drag/reshape) | via edit delete | **yes** |
|
|
120
|
+
| `multipoint` | add **many** markers | drag each marker | via edit delete | no |
|
|
121
|
+
| `multipolygon` | add **many** polygons | draw-edit each | via edit delete | no |
|
|
122
|
+
| `geometrycollection` | **many** points **and** polygons | both | both | no |
|
|
123
|
+
|
|
124
|
+
- For the **single-instance** types (`point`, `polygon`), once one shape exists the
|
|
125
|
+
corresponding "add" tool is hidden from the toolbar — the shape can be **moved** and
|
|
126
|
+
**removed**, and removing it re-enables adding.
|
|
127
|
+
- Points (`point`, `multipoint`, and the points in `geometrycollection`) are **draggable**
|
|
128
|
+
markers: drag to move, and the new coordinates are written back immediately.
|
|
129
|
+
- Polygons (and the polygon parts of `multipolygon` / `geometrycollection`) use
|
|
130
|
+
Leaflet-Draw's edit control to **move/resize** and its delete tool to remove.
|
|
131
|
+
|
|
132
|
+
### Server side
|
|
133
|
+
|
|
134
|
+
| Model field (drf-react-by-schema) | Schema `type` | GeoJSON |
|
|
135
|
+
| --- | --- | --- |
|
|
136
|
+
| `fields.PointField` | `point` | `Point` |
|
|
137
|
+
| `fields.PolygonField` | `polygon` | `Polygon` |
|
|
138
|
+
| `fields.MultiPointField` | `multipoint` | `MultiPoint` |
|
|
139
|
+
| `fields.MultiPolygonField` | `multipolygon` | `MultiPolygon` |
|
|
140
|
+
| `fields.GeometryCollectionField` | `geometrycollection` | `GeometryCollection` |
|
|
141
|
+
|
|
142
|
+
> Tip: after adding/editing these model fields, run Django's `manage.py sync_react` to
|
|
143
|
+
> regenerate the TypeScript types — geometry fields map to the corresponding GeoJSON
|
|
144
|
+
> types (`Point`, `Polygon`, `MultiPoint`, `MultiPolygon`, `GeometryCollection`) rather
|
|
145
|
+
> than `any`.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Localization & theming
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`drf-react-by-schema` exposes its user-facing strings through the **MUI theme**, so a
|
|
6
|
+
consuming app can localize (translate) every widget in **one place** — its own theme.
|
|
7
|
+
This follows MUI's own approach (MUI components take `localeText`/`locale` props and the
|
|
8
|
+
DataGrid locale lives on the theme), keeps the strings out of `DRFReactBySchemaProvider`,
|
|
9
|
+
and works out of the box with sensible defaults.
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
- The package **augments the MUI theme** (`@mui/material/styles`) with a typed `texts`
|
|
14
|
+
field (`Theme.texts` / `ThemeOptions.texts`).
|
|
15
|
+
- Package **defaults** (English) are baked into the package's **default theme**; apps
|
|
16
|
+
override any namespace they want in their own theme.
|
|
17
|
+
- `DRFReactBySchemaProvider` **deepmerges** the app's `theme` onto that default theme
|
|
18
|
+
(`deepmerge(defaultTheme, theme)`), so an app only sets the keys it wants.
|
|
19
|
+
- Widgets read the texts through `useTheme()` plus the exported `getThemeTexts(theme)`
|
|
20
|
+
helper, falling back to the built-in defaults when `theme.texts` is absent.
|
|
21
|
+
|
|
22
|
+
Because everything lives in the theme, it works under `DRFReactBySchemaProvider` **and**
|
|
23
|
+
under any plain `ThemeProvider` (texts simply fall back to the package defaults).
|
|
24
|
+
|
|
25
|
+
## Available texts (namespaces)
|
|
26
|
+
|
|
27
|
+
The `texts` shape is extensible — add new namespaces over time. Currently:
|
|
28
|
+
|
|
29
|
+
| Namespace | Used by | Keys |
|
|
30
|
+
| --------- | ------- | ---- |
|
|
31
|
+
| `texts.geometry` | geometry picker (`MapGeometryPicker`) and grid edit/thumb cells (`GeoEditCell`) | `labelPoint`, `labelOther`, `malformed`, `synced`, `edit`, `define`, `drawSelectTitle`, `close` |
|
|
32
|
+
| `texts.forms` | form action buttons (`FormActions`, `DialogFormBySchema`) and dialogs (`DialogActions`) | `save`, `saveAndContinue`, `saveAndCreateNew`, `cancel`, `remove`, `dialogCancel`, `dialogSave` |
|
|
33
|
+
| `texts.richText` | rich-text toolbar (`RichTextEditorInput`) and link dialog | `paragraph`, `heading1`–`heading3`, `bold`, `italic`, `underline`, `strike`, `bulletList`, `orderedList`, `blockquote`, `codeBlock`, `horizontalRule`, `link`, `linkTitle`, `linkUrl`, `linkApply`, `linkRemove`, `linkCancel`, `clearFormatting`, `undo`, `redo` |
|
|
34
|
+
|
|
35
|
+
## Setting texts in your app
|
|
36
|
+
|
|
37
|
+
Add a `texts` key to the `theme` you pass to `DRFReactBySchemaProvider` (or to your own
|
|
38
|
+
`ThemeProvider`). The provider deepmerges it, so you can provide only the keys you want
|
|
39
|
+
to change.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
// theme.ts
|
|
43
|
+
import { createTheme } from '@mui/material/styles'
|
|
44
|
+
|
|
45
|
+
export const theme = createTheme({
|
|
46
|
+
// ...palette, typography, etc.
|
|
47
|
+
texts: {
|
|
48
|
+
geometry: {
|
|
49
|
+
labelPoint: 'Coordenadas (GeoJSON)',
|
|
50
|
+
labelOther: 'GeoJSON (cole aqui)',
|
|
51
|
+
malformed: 'Dados malformados, corrija o GeoJSON para refletir no mapa.',
|
|
52
|
+
synced: 'O GeoJSON abaixo é sincronizado com o mapa.',
|
|
53
|
+
edit: 'Editar geometria',
|
|
54
|
+
define: 'Definir geometria',
|
|
55
|
+
drawSelectTitle: 'Desenhar / selecionar geometria',
|
|
56
|
+
close: 'Fechar',
|
|
57
|
+
},
|
|
58
|
+
forms: {
|
|
59
|
+
save: 'Salvar',
|
|
60
|
+
saveAndContinue: 'Salvar e continuar',
|
|
61
|
+
saveAndCreateNew: 'Salvar e criar novo',
|
|
62
|
+
cancel: 'Cancelar',
|
|
63
|
+
remove: 'Apagar',
|
|
64
|
+
dialogCancel: 'Cancelar',
|
|
65
|
+
dialogSave: 'Salvar',
|
|
66
|
+
},
|
|
67
|
+
richText: {
|
|
68
|
+
paragraph: 'Parágrafo',
|
|
69
|
+
heading1: 'Título 1',
|
|
70
|
+
heading2: 'Título 2',
|
|
71
|
+
heading3: 'Título 3',
|
|
72
|
+
bold: 'Negrito',
|
|
73
|
+
italic: 'Itálico',
|
|
74
|
+
underline: 'Sublinhado',
|
|
75
|
+
strike: 'Riscado',
|
|
76
|
+
bulletList: 'Lista com marcadores',
|
|
77
|
+
orderedList: 'Lista numerada',
|
|
78
|
+
blockquote: 'Citação',
|
|
79
|
+
codeBlock: 'Bloco de código',
|
|
80
|
+
horizontalRule: 'Linha horizontal',
|
|
81
|
+
link: 'Link',
|
|
82
|
+
linkTitle: 'Inserir link',
|
|
83
|
+
linkUrl: 'URL',
|
|
84
|
+
linkApply: 'Aplicar',
|
|
85
|
+
linkRemove: 'Remover link',
|
|
86
|
+
linkCancel: 'Cancelar',
|
|
87
|
+
clearFormatting: 'Limpar formatação',
|
|
88
|
+
undo: 'Desfazer',
|
|
89
|
+
redo: 'Refazer',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// App.js
|
|
95
|
+
import { DRFReactBySchemaProvider } from 'drf-react-by-schema'
|
|
96
|
+
import { theme } from './theme'
|
|
97
|
+
|
|
98
|
+
<DRFReactBySchemaProvider serverEndPoint={...} schema={...} theme={theme}>
|
|
99
|
+
<YourWonderfulApp />
|
|
100
|
+
</DRFReactBySchemaProvider>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
TypeScript picks up the `texts` keys automatically from the theme augmentation; you can
|
|
104
|
+
also import and reuse the text types (`DRFReactBySchemaTexts`, `GeometryTexts`,
|
|
105
|
+
`FormTexts`) from `drf-react-by-schema` for reference.
|
|
106
|
+
|
|
107
|
+
## Precedence
|
|
108
|
+
|
|
109
|
+
Strings resolve in this order, so the most specific wins:
|
|
110
|
+
|
|
111
|
+
1. **Explicit component prop** — e.g. `FormActions` has `labelSave`/`labelCancel`/
|
|
112
|
+
`labelRemove`/`labelSaveAndContinue`/`labelSaveAndCreateNew`; `DialogActions` has
|
|
113
|
+
`btnCancel`/`btnConfirm`; `MapGeometryPicker` has `label`/`helperText`.
|
|
114
|
+
2. **`theme.texts.<namespace>`** — set app-wide in your theme.
|
|
115
|
+
3. **Package `DEFAULT_TEXTS`** — the built-in fallback.
|
|
116
|
+
|
|
117
|
+
Since the explicit props still exist, both strategies can coexist: pass per-component
|
|
118
|
+
strings for a one-off button, or set theme texts for whole-app localization.
|
|
119
|
+
|
|
120
|
+
## MUI component localization
|
|
121
|
+
|
|
122
|
+
The package's default theme already localizes the MUI components it renders to
|
|
123
|
+
**pt-BR** by composing MUI's official locale packages:
|
|
124
|
+
|
|
125
|
+
- `@mui/material/locale` (core components)
|
|
126
|
+
- `@mui/x-data-grid/locales` (DataGrid), plus DataGrid `localeText` overrides for the
|
|
127
|
+
package's custom toolbar strings (`Buscar...`, `Limpar busca`, …)
|
|
128
|
+
- `@mui/x-date-pickers/locales` (date pickers)
|
|
129
|
+
|
|
130
|
+
Date/time inputs additionally wrap each field in
|
|
131
|
+
`<LocalizationProvider adapterLocale="pt-br">`. To switch to another language, set the
|
|
132
|
+
corresponding MUI locale + DataGrid `localeText` in your theme and the desired
|
|
133
|
+
`adapterLocale` on the picker inputs.
|
|
134
|
+
|
|
135
|
+
## Markers (geometry)
|
|
136
|
+
|
|
137
|
+
By default the geometry widgets render points with a **built-in inline-SVG marker
|
|
138
|
+
icon** (a data-URI), so markers appear out of the box under any bundler — Leaflet's
|
|
139
|
+
stock page-relative PNGs 404 inside an SPA. To use a custom marker, call the imperative
|
|
140
|
+
helper once at app startup (it always applies/overrides):
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { applyDefaultLeafletIcon } from 'drf-react-by-schema'
|
|
144
|
+
|
|
145
|
+
applyDefaultLeafletIcon({
|
|
146
|
+
iconUrl: '/assets/my-marker.png',
|
|
147
|
+
iconSize: [30, 42],
|
|
148
|
+
iconAnchor: [15, 42],
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
If you don't call it, the package's widgets apply the built-in default the first time it
|
|
153
|
+
is needed (and never override an icon you already placed on `L.Icon.Default`). Because
|
|
154
|
+
the application is one-shot, call it before rendering your maps; subsequent calls without
|
|
155
|
+
arguments are no-ops.
|
|
156
|
+
|
|
157
|
+
## Reading texts in custom components
|
|
158
|
+
|
|
159
|
+
If you build custom widgets that should share the same themed strings, use the exported
|
|
160
|
+
`getThemeTexts(theme)` helper (returns theme texts merged over the package defaults):
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import { useTheme } from '@mui/material/styles'
|
|
164
|
+
import { getThemeTexts } from 'drf-react-by-schema'
|
|
165
|
+
|
|
166
|
+
function MyActions() {
|
|
167
|
+
const formTexts = getThemeTexts(useTheme()).forms
|
|
168
|
+
return <button>{formTexts.save}</button>
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The text types (`DRFReactBySchemaTexts`, `GeometryTexts`, `FormTexts`) are exported from
|
|
173
|
+
`drf-react-by-schema` too, so you can type helper objects separately.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Password reset, change password & e-mail setup
|
|
2
|
+
|
|
3
|
+
End-to-end guide for the **forgot-password / change-password** flow on a
|
|
4
|
+
Django + DRF + simplejwt backend with this React package.
|
|
5
|
+
|
|
6
|
+
The **React** side ships:
|
|
7
|
+
|
|
8
|
+
- `ForgotPasswordForm` — works in two modes:
|
|
9
|
+
- *request*: asks for the e-mail and calls `requestPasswordReset`.
|
|
10
|
+
- *confirm*: given `uid` + `token` (from the e-mail link), sets a new password
|
|
11
|
+
via `confirmPasswordReset`.
|
|
12
|
+
- `ChangePasswordForm` — for an already signed-in user (current password + new
|
|
13
|
+
password), backed by `changePassword`.
|
|
14
|
+
- `useAPIWrapper()` exposes: `requestPasswordReset(email)`,
|
|
15
|
+
`validatePasswordReset({ uid, token })`,
|
|
16
|
+
`confirmPasswordReset({ uid, token, new_password })`, `changePassword({ old_password, new_password })`.
|
|
17
|
+
`validatePasswordReset` resolves the account behind a reset link (name +
|
|
18
|
+
`has_usable_password`) without changing anything, so the page can greet the user
|
|
19
|
+
by name and tell a first-time invite (no usable password yet) from a reset.
|
|
20
|
+
|
|
21
|
+
Everything is mobile-friendly (the forms use `fullWidth` fields) and lives under
|
|
22
|
+
`DRFReactBySchemaProvider`, so the helpers are typed through `useAPIWrapper()`.
|
|
23
|
+
|
|
24
|
+
## 1. Server contract (provided by drf-react-by-schema-django)
|
|
25
|
+
|
|
26
|
+
The Django package exposes these endpoints (auto-registered):
|
|
27
|
+
|
|
28
|
+
| Method | Endpoint | Auth | Body |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| POST | `password_reset/request/` | none | `{ email }` |
|
|
31
|
+
| POST | `password_reset/validate/` | none | `{ uid, token }` |
|
|
32
|
+
| POST | `password_reset/confirm/` | none | `{ uid, token, new_password }` |
|
|
33
|
+
| POST | `my_account/change_password/` | JWT | `{ old_password, new_password }` |
|
|
34
|
+
|
|
35
|
+
It reuses `django.contrib.auth` for token generation and password hashing, so no
|
|
36
|
+
extra package (allauth / dj-rest-auth) is required. See the Django package README
|
|
37
|
+
for the full details of the pluggable delivery callback.
|
|
38
|
+
|
|
39
|
+
## 2. Frontend wiring (no extra config needed)
|
|
40
|
+
|
|
41
|
+
`DRFReactBySchemaProvider` derives `requestPasswordReset`, `validatePasswordReset`,
|
|
42
|
+
`confirmPasswordReset` and `changePassword` from your `serverEndPoint` automatically
|
|
43
|
+
(`{api}/password_reset/…`, `{api}/my_account/change_password/`). You only need the
|
|
44
|
+
usual `url`/`apiTokenUrl`, e.g.:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const serverEndPoint = {
|
|
48
|
+
url: 'http://localhost:8000',
|
|
49
|
+
apiTokenUrl: 'http://localhost:8000/api/token/',
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Example — a public reset page reached both from the login screen and from the
|
|
54
|
+
e-mail link (`/esqueci-senha?uid=..&token=..`):
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { ForgotPasswordForm } from 'drf-react-by-schema'
|
|
58
|
+
|
|
59
|
+
function ForgotPasswordPage() {
|
|
60
|
+
const [params] = useSearchParams()
|
|
61
|
+
const navigate = useNavigate()
|
|
62
|
+
const uid = params.get('uid') ?? undefined
|
|
63
|
+
const token = params.get('token') ?? undefined
|
|
64
|
+
return (
|
|
65
|
+
<ForgotPasswordForm
|
|
66
|
+
uid={uid} // present -> confirm mode
|
|
67
|
+
token={token}
|
|
68
|
+
onBack={() => navigate('/')}
|
|
69
|
+
onSuccess={() => navigate('/')}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
An authenticated "change my password" screen:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { ChangePasswordForm } from 'drf-react-by-schema'
|
|
79
|
+
// <ChangePasswordForm onSuccess={() => signOut()} />
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 3. Delivering the e-mail (the "e-mail infrastructure")
|
|
83
|
+
|
|
84
|
+
Sending is handled by the **backend** — the React app only composes the reset
|
|
85
|
+
link `{scheme}://{host}\{path}?uid=..&token=..`. Two levels of configuration:
|
|
86
|
+
|
|
87
|
+
**Simple (no code):** configure Django e-mail settings and the reset link path;
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
# settings.py / settings_local.py
|
|
91
|
+
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" # or console in dev
|
|
92
|
+
EMAIL_HOST = "smtp.your-provider.com"
|
|
93
|
+
EMAIL_PORT = 587
|
|
94
|
+
EMAIL_USE_TLS = True
|
|
95
|
+
EMAIL_HOST_USER = "no-reply@your-domain"
|
|
96
|
+
EMAIL_HOST_PASSWORD = "…"
|
|
97
|
+
DEFAULT_FROM_EMAIL = "Your App <no-reply@your-domain>"
|
|
98
|
+
|
|
99
|
+
PASSWORD_RESET_SCHEME = "https"
|
|
100
|
+
PASSWORD_RESET_DOMAIN = "app.your-domain" # avoids trusting request scheme behind a proxy
|
|
101
|
+
PASSWORD_RESET_CONFIRM_PATH = "/esqueci-senha" # your page
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
With that, `drf-react-by-schema-django` sends a plain-text message via Django's
|
|
105
|
+
mail backend.
|
|
106
|
+
|
|
107
|
+
**Customised (still small):** point `DRF_REACT_BY_SCHEMA['PASSWORD_RESET_CALLBACK']`
|
|
108
|
+
at a callable `(request, user, uid, token)`. The library already mints
|
|
109
|
+
`uid`/`token` before invoking it, so the change is a single function (branding,
|
|
110
|
+
HTML, another channel, queueing…):
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# myapp/auth.py
|
|
114
|
+
def send_reset_email(request, user, uid, token):
|
|
115
|
+
send_mail("Redefina sua senha", f"Link: https://app.suaorg.org/esqueci-senha?uid={uid}&token={token}", None, [user.email])
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
DRF_REACT_BY_SCHEMA = { ...
|
|
120
|
+
"PASSWORD_RESET_CALLBACK": "myapp.auth.send_reset_email",
|
|
121
|
+
"PASSWORD_RESET_CONFIRM_PATH": "/esqueci-senha",
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Dev vs prod
|
|
126
|
+
|
|
127
|
+
- **Dev**: `EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"` prints
|
|
128
|
+
the message to the terminal — great to test the flow without real SMTP.
|
|
129
|
+
- **Prod (Ubuntu/VPS)**: point `EMAIL_BACKEND` to a real SMTP host (transactional
|
|
130
|
+
provider recommended), set `EMAIL_*`, fix `PASSWORD_RESET_DOMAIN`/`SCHEME` and
|
|
131
|
+
list the domain in `ALLOWED_HOSTS`. Keep real credentials out of the repo
|
|
132
|
+
(e.g. in a gitignored `settings_local.py` + a gitignored server guide).
|
|
133
|
+
|
|
134
|
+
## 4. Real-world flow
|
|
135
|
+
|
|
136
|
+
1. User taps "Esqueci minha senha" on the login → goes to the public page.
|
|
137
|
+
2. Page calls `requestPasswordReset(email)` → backend mints `uid`/`token` and
|
|
138
|
+
fires the callback → e-mail with the link.
|
|
139
|
+
3. User opens the link (`…?uid=..&token=..`) → the same page is in *confirm* mode
|
|
140
|
+
→ `confirmPasswordReset({ uid, token, new_password })` sets the password.
|
|
141
|
+
4. (Optional) sign-in and use `ChangePasswordForm` → `changePassword(...)`.
|
|
142
|
+
|
|
143
|
+
Inviting a new user works the same way: create the account with an **unusable
|
|
144
|
+
password** on the backend and fire `requestPasswordReset(email)` so the invited
|
|
145
|
+
person receives a link to set their own password.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drf-react-by-schema",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.27",
|
|
4
4
|
"description": "Components and Tools for building a React App having Django Rest Framework (DRF) as server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -132,6 +132,7 @@
|
|
|
132
132
|
"not dead"
|
|
133
133
|
],
|
|
134
134
|
"files": [
|
|
135
|
-
"dist"
|
|
135
|
+
"dist",
|
|
136
|
+
"docs"
|
|
136
137
|
]
|
|
137
138
|
}
|