Adding support for nested types, array and dates!
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
from pydantic import Field
|
||||
from datetime import datetime, date
|
||||
from typing import List, Literal
|
||||
|
||||
from beanie import Document
|
||||
from pydantic import Field, BaseModel
|
||||
from beanie import Document, Link
|
||||
|
||||
|
||||
class EntityType(str, Enum):
|
||||
@@ -11,13 +12,40 @@ class EntityType(str, Enum):
|
||||
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):
|
||||
_id: str
|
||||
type: EntityType
|
||||
name: str
|
||||
address: str
|
||||
entity_data: Individual | Corporation = Field(..., discriminator='type')
|
||||
|
||||
created_at: datetime = Field(default=datetime.utcnow(), nullable=False)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
||||
#
|
||||
# class Settings:
|
||||
# name = "entities"
|
||||
|
||||
class Settings:
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -12,11 +12,12 @@ class EntityRead(Entity):
|
||||
|
||||
|
||||
class EntityCreate(Writer):
|
||||
type: EntityType
|
||||
name: str
|
||||
address: str
|
||||
entity_data: Individual | Corporation = Field(..., discriminator='type')
|
||||
|
||||
|
||||
class EntityUpdate(BaseModel):
|
||||
name: str
|
||||
address: str
|
||||
entity_data: Individual | Corporation = Field(..., discriminator='type')
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<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>
|
||||
<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" }}
|
||||
</button>
|
||||
<button *ngIf="!this.isCreateForm()" (click)="onDelete()" color="danger" type="" class="btn btn-default">
|
||||
<button cButton *ngIf="!this.isCreateForm()" (click)="onDelete()" color="danger">
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
//import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
|
||||
import { HttpClientModule } from "@angular/common/http";
|
||||
|
||||
import { FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
@@ -11,17 +9,23 @@ import { FormlyBootstrapModule } from '@ngx-formly/bootstrap';
|
||||
|
||||
import { CardComponent } from './card/card.component';
|
||||
import { ListComponent } from './list/list.component';
|
||||
import { ObjectTypeComponent } from "./object.type";
|
||||
import {ApiService, CrudService} from "./crud.service";
|
||||
import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {JsonschemasService} from "./jsonschemas.service";
|
||||
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 { NgbModule} from "@ng-bootstrap/ng-bootstrap";
|
||||
import { JsonschemasService } from "./jsonschemas.service";
|
||||
import { MultiSchemaTypeComponent } from "./types/multischema.type";
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CardComponent,
|
||||
ListComponent,
|
||||
ObjectTypeComponent
|
||||
ObjectTypeComponent,
|
||||
DatetimeTypeComponent,
|
||||
ArrayTypeComponent,
|
||||
MultiSchemaTypeComponent
|
||||
],
|
||||
providers: [ JsonschemasService, ApiService, CrudService ],
|
||||
imports: [
|
||||
@@ -34,6 +38,9 @@ import {JsonschemasService} from "./jsonschemas.service";
|
||||
FormlyModule.forRoot({
|
||||
types: [
|
||||
{ name: 'object', component: ObjectTypeComponent },
|
||||
{ name: 'datetime', component: DatetimeTypeComponent },
|
||||
{ name: 'array', component: ArrayTypeComponent },
|
||||
{ name: 'multischema', component: MultiSchemaTypeComponent },
|
||||
]
|
||||
}),
|
||||
FormlyBootstrapModule
|
||||
|
||||
@@ -32,15 +32,35 @@ export class JsonschemasService {
|
||||
for (let prop_name in resource.properties) {
|
||||
let prop = resource.properties[prop_name];
|
||||
|
||||
if (this.is_reference(prop)) {
|
||||
let subresourceName = this.get_reference_name(prop);
|
||||
resource.components.schemas[subresourceName] = this.buildResource(subresourceName);
|
||||
if (prop_name === '_id') {
|
||||
delete resource.properties[prop_name]
|
||||
} 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;
|
||||
}
|
||||
|
||||
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> {
|
||||
return new Observable<Schema>((observer) => {
|
||||
this.getSchemas().subscribe(() => {
|
||||
@@ -75,6 +95,10 @@ export class JsonschemasService {
|
||||
return prop.hasOwnProperty('$ref');
|
||||
}
|
||||
|
||||
private is_union(prop: any) {
|
||||
return prop.hasOwnProperty('oneOf');
|
||||
}
|
||||
|
||||
private get_reference_name(prop: any) {
|
||||
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