Adding support for nested types, array and dates!
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 { ObjectTypeComponent } from "./types/object.type";
|
||||||
|
import { DatetimeTypeComponent } from "./types/datetime.type";
|
||||||
import { ApiService, CrudService } from "./crud.service";
|
import { ApiService, CrudService } from "./crud.service";
|
||||||
import { NgbModule} from "@ng-bootstrap/ng-bootstrap";
|
import { NgbModule} from "@ng-bootstrap/ng-bootstrap";
|
||||||
import { JsonschemasService } from "./jsonschemas.service";
|
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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
36
front/app/src/common/crud/types/array.type.ts
Normal file
36
front/app/src/common/crud/types/array.type.ts
Normal 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 */
|
||||||
89
front/app/src/common/crud/types/datetime.type.ts
Normal file
89
front/app/src/common/crud/types/datetime.type.ts
Normal 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')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
38
front/app/src/common/crud/types/multischema.type.ts
Normal file
38
front/app/src/common/crud/types/multischema.type.ts
Normal 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 */
|
||||||
Reference in New Issue
Block a user