Adding support for nested types, array and dates!

This commit is contained in:
2023-01-19 18:39:51 +01:00
parent aa4399ea20
commit 70d5a6e752
9 changed files with 246 additions and 23 deletions

View File

@@ -1,8 +1,9 @@
from enum import Enum from enum import Enum
from datetime import datetime from datetime import datetime, date
from pydantic import Field from typing import List, Literal
from beanie import Document from pydantic import Field, BaseModel
from beanie import Document, Link
class EntityType(str, Enum): class EntityType(str, Enum):
@@ -11,13 +12,40 @@ class EntityType(str, Enum):
institution = 'institution' institution = 'institution'
class Individual(BaseModel):
type: Literal['individual'] = 'individual'
firstname: str
middlenames: List[str] = Field(default=[])
lastname: str
surnames: List[str] = Field(default=[])
day_of_birth: date
job: str
employer: str
class Employee(BaseModel):
entity_id: str
role: str
class Corporation(BaseModel):
type: Literal['corporation'] = 'corporation'
title: str
activity: str
employees: List[Employee] = Field(default=[])
class Entity(Document): class Entity(Document):
_id: str _id: str
type: EntityType
name: str name: str
address: str address: str
entity_data: Individual | Corporation = Field(..., discriminator='type')
created_at: datetime = Field(default=datetime.utcnow(), nullable=False) created_at: datetime = Field(default=datetime.utcnow(), nullable=False)
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False) updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
#
# class Settings: class Settings:
# name = "entities" bson_encoders = {
date: lambda dt: datetime(year=dt.year, month=dt.month, day=dt.day, hour=0, minute=0, second=0) \
if not hasattr(dt, 'hour') else dt
}

View File

@@ -1,9 +1,9 @@
import uuid import uuid
from datetime import datetime from datetime import datetime
from pydantic import BaseModel from pydantic import BaseModel, Field
from .models import Entity, EntityType from .models import Entity, EntityType, Individual, Corporation
from ..core.schemas import Writer from ..core.schemas import Writer
@@ -12,11 +12,12 @@ class EntityRead(Entity):
class EntityCreate(Writer): class EntityCreate(Writer):
type: EntityType
name: str name: str
address: str address: str
entity_data: Individual | Corporation = Field(..., discriminator='type')
class EntityUpdate(BaseModel): class EntityUpdate(BaseModel):
name: str name: str
address: str address: str
entity_data: Individual | Corporation = Field(..., discriminator='type')

View File

@@ -1,11 +1,11 @@
<div> <div>
<form [formGroup]="form" (ngSubmit)="onSubmit(model)"> <form cForm [formGroup]="form" (ngSubmit)="onSubmit(model)">
<span class="col col-form-label" *ngIf="loading$ | async">Loading...</span> <span class="col col-form-label" *ngIf="loading$ | async">Loading...</span>
<formly-form [form]="form" [fields]="fields" [model]="model"></formly-form> <formly-form [form]="form" [fields]="fields" [model]="model"></formly-form>
<button color="primary" type="submit" class="btn btn-default"> <button cButton color="primary">
{{ this.isCreateForm() ? "Create" : "Update" }} {{ this.isCreateForm() ? "Create" : "Update" }}
</button> </button>
<button *ngIf="!this.isCreateForm()" (click)="onDelete()" color="danger" type="" class="btn btn-default"> <button cButton *ngIf="!this.isCreateForm()" (click)="onDelete()" color="danger">
Delete Delete
</button> </button>
</form> </form>

View File

@@ -1,8 +1,6 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
//import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { HttpClientModule } from "@angular/common/http"; import { HttpClientModule } from "@angular/common/http";
import { FormsModule, ReactiveFormsModule} from '@angular/forms'; import { FormsModule, ReactiveFormsModule} from '@angular/forms';
@@ -11,17 +9,23 @@ import { FormlyBootstrapModule } from '@ngx-formly/bootstrap';
import { CardComponent } from './card/card.component'; import { CardComponent } from './card/card.component';
import { ListComponent } from './list/list.component'; import { ListComponent } from './list/list.component';
import { ObjectTypeComponent } from "./object.type"; import { ArrayTypeComponent } from "./types/array.type";
import {ApiService, CrudService} from "./crud.service"; import { ObjectTypeComponent } from "./types/object.type";
import {NgbModule} from "@ng-bootstrap/ng-bootstrap"; import { DatetimeTypeComponent } from "./types/datetime.type";
import {JsonschemasService} from "./jsonschemas.service"; import { ApiService, CrudService } from "./crud.service";
import { NgbModule} from "@ng-bootstrap/ng-bootstrap";
import { JsonschemasService } from "./jsonschemas.service";
import { MultiSchemaTypeComponent } from "./types/multischema.type";
@NgModule({ @NgModule({
declarations: [ declarations: [
CardComponent, CardComponent,
ListComponent, ListComponent,
ObjectTypeComponent ObjectTypeComponent,
DatetimeTypeComponent,
ArrayTypeComponent,
MultiSchemaTypeComponent
], ],
providers: [ JsonschemasService, ApiService, CrudService ], providers: [ JsonschemasService, ApiService, CrudService ],
imports: [ imports: [
@@ -34,6 +38,9 @@ import {JsonschemasService} from "./jsonschemas.service";
FormlyModule.forRoot({ FormlyModule.forRoot({
types: [ types: [
{ name: 'object', component: ObjectTypeComponent }, { name: 'object', component: ObjectTypeComponent },
{ name: 'datetime', component: DatetimeTypeComponent },
{ name: 'array', component: ArrayTypeComponent },
{ name: 'multischema', component: MultiSchemaTypeComponent },
] ]
}), }),
FormlyBootstrapModule FormlyBootstrapModule

View File

@@ -32,15 +32,35 @@ export class JsonschemasService {
for (let prop_name in resource.properties) { for (let prop_name in resource.properties) {
let prop = resource.properties[prop_name]; let prop = resource.properties[prop_name];
if (this.is_reference(prop)) { if (prop_name === '_id') {
let subresourceName = this.get_reference_name(prop); delete resource.properties[prop_name]
resource.components.schemas[subresourceName] = this.buildResource(subresourceName); } else if (this.is_reference(prop)) {
this.resolveReference(resource, prop);
} else if (prop.hasOwnProperty('oneOf')) {
for (let i in prop.oneOf) {
this.resolveReference(resource, prop.oneOf[i]);
}
} else if (prop.hasOwnProperty('items') && this.is_reference(prop.items)) {
this.resolveReference(resource, prop.items);
} else if (prop.format === 'date-time') {
prop.type = "datetime";
} }
} }
return resource; return resource;
} }
resolveReference(resource: any, prop_reference: any) {
let subresourceName = this.get_reference_name(prop_reference);
let subresource = this.buildResource(subresourceName);
resource.components.schemas[subresourceName] = subresource;
for (let subsubresourceName in subresource.components.schemas) {
if (! resource.components.schemas.hasOwnProperty(subsubresourceName)) {
resource.components.schemas[subsubresourceName] = subresource.components.schemas[subsubresourceName];
}
}
}
getCreateResource(resourceName: string): Observable<Schema> { getCreateResource(resourceName: string): Observable<Schema> {
return new Observable<Schema>((observer) => { return new Observable<Schema>((observer) => {
this.getSchemas().subscribe(() => { this.getSchemas().subscribe(() => {
@@ -75,6 +95,10 @@ export class JsonschemasService {
return prop.hasOwnProperty('$ref'); return prop.hasOwnProperty('$ref');
} }
private is_union(prop: any) {
return prop.hasOwnProperty('oneOf');
}
private get_reference_name(prop: any) { private get_reference_name(prop: any) {
return prop['$ref'].substring(prop['$ref'].lastIndexOf('/')+1); return prop['$ref'].substring(prop['$ref'].lastIndexOf('/')+1);
} }

View File

@@ -0,0 +1,36 @@
import { Component } from '@angular/core';
import { FieldArrayType } from '@ngx-formly/core';
@Component({
selector: 'formly-array-type',
template: `
<div class="mb-3">
<label *ngIf="props.label" class="form-label">{{ props.label }}</label>
<p *ngIf="props.description">{{ props.description }}</p>
<div class="d-flex flex-row-reverse">
<button class="btn btn-primary" type="button" (click)="add()">+</button>
</div>
<div class="alert alert-danger" role="alert" *ngIf="showError && formControl.errors">
<formly-validation-message [field]="field"></formly-validation-message>
</div>
<div *ngFor="let field of field.fieldGroup; let i = index" class="row align-items-start">
<formly-field class="col" [field]="field"></formly-field>
<div *ngIf="field.props!['removable'] !== false" class="col-2 text-right">
<button class="btn btn-danger" type="button" (click)="remove(i)">-</button>
</div>
</div>
</div>
`,
})
export class ArrayTypeComponent extends FieldArrayType {
constructor() {
super();
}
}
/** Copyright 2021 Formly. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at https://github.com/ngx-formly/ngx-formly/blob/main/LICENSE */

View File

@@ -0,0 +1,89 @@
import { Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import { formatDate } from "@angular/common";
import { NgbDateStruct, NgbTimeStruct } from '@ng-bootstrap/ng-bootstrap';
import { FieldType, FieldTypeConfig } from '@ngx-formly/core';
@Component({
selector: 'app-form-datepicker-type',
template: `
<label *ngIf="props.label && props['hideLabel'] !== true" [attr.for]="id"
class="form-label">{{ props.label }}
<span *ngIf="props.required && props['hideRequiredMarker'] !== true" aria-hidden="true">*</span>
</label>
<div class="input-group" *ngIf="! this.field.props.readonly">
<input type="text"
[formControl]="formControl"
[formlyAttributes]="field"
[class.is-invalid]="showError"
/>
<input
class="form-control"
placeholder="yyyy-mm-dd"
name="dp"
[(ngModel)]="date"
(ngModelChange)="changeDatetime($event)"
ngbDatepicker
#d="ngbDatepicker"
/>
<button class="btn btn-outline-secondary bi bi-calendar3" (click)="d.toggle()" type="button"></button>
<ngb-timepicker
(ngModelChange)="changeDatetime($event)"
[(ngModel)]="time"
[seconds]="true">
</ngb-timepicker>
</div>
<div class="input-group" *ngIf="this.field.props.readonly">
<input class="form-control" value="{{ this.datetime.toLocaleString() }}" disabled=""/>
</div>
`,
})
export class DatetimeTypeComponent extends FieldType<FieldTypeConfig> implements OnInit
{
public time : NgbTimeStruct = { hour: 12, minute: 0, second: 0 }
public date : NgbDateStruct = { year: 2023, month: 1, day: 9 }
public datetime : Date = new Date()
constructor() {
super();
}
ngOnInit() {
this.formControl.valueChanges.subscribe(value => {
this.datetime = new Date(value)
this.date = this.getDateStruct(this.datetime);
this.time = this.getTimeStruct(this.datetime);
})
}
getDateStruct(d: Date) {
return {
year: d.getFullYear(),
month: d.getMonth() + 1,
day: d.getDate(),
}
}
getTimeStruct(d: Date) {
return {
hour: d.getHours(),
minute: d.getMinutes(),
second: d.getSeconds(),
}
}
changeDatetime(event: any) {
this.datetime.setFullYear(this.date.year)
this.datetime.setMonth(this.date.month - 1)
this.datetime.setDate(this.date.day)
this.datetime.setHours(this.time.hour)
this.datetime.setMinutes(this.time.minute)
this.datetime.setSeconds(this.time.second)
this.formControl.setValue(
formatDate(this.datetime, 'YYYY-MM-ddTHH:mm:ss.SSS', 'EN_US', 'CET')
)
}
}

View File

@@ -0,0 +1,38 @@
import {Component, OnInit} from '@angular/core';
import { FieldType } from '@ngx-formly/core';
import {Observable} from "rxjs";
@Component({
selector: 'formly-multi-schema-type',
template: `
<div class="card mb-3">
<div class="card-body">
<label *ngIf="props.label" class="form-label">{{ props.label }}</label>
<p *ngIf="props.description">{{ props.description }}</p>
<div class="alert alert-danger" role="alert" *ngIf="showError && formControl.errors">
<formly-validation-message [field]="field"></formly-validation-message>
</div>
<formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
</div>
</div>
`,
})
export class MultiSchemaTypeComponent extends FieldType implements OnInit {
ngOnInit() {
let f = this.field
f.fieldGroup![0].props!.options!.forEach(function (option) {
option.label = f.fieldGroup![1].fieldGroup![option.value].props!.label;
f.fieldGroup![1].fieldGroup![option.value].props!.label = "";
});
f.fieldGroup![1].fieldGroup!.forEach(function (field) {
//field.fieldGroup![0].hide = true;
});
}
}
/** Copyright 2021 Formly. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at https://github.com/ngx-formly/ngx-formly/blob/main/LICENSE */