summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/frest/auth/__init__.py0
-rw-r--r--src/frest/auth/forms.py10
-rw-r--r--src/frest/auth/models.py52
-rw-r--r--src/frest/auth/routes.py183
-rw-r--r--src/frest/database.py4
-rw-r--r--src/frest/decorators.py40
-rw-r--r--src/frest/forms.py20
-rw-r--r--src/frest/mail.py11
-rw-r--r--src/frest/manage.py23
-rw-r--r--src/frest/manage/__init__.py0
-rw-r--r--src/frest/manage/bcolors.py17
-rw-r--r--src/frest/manage/utils.py226
-rw-r--r--src/frest/templates/form.txt11
-rw-r--r--src/frest/templates/models.txt26
-rw-r--r--src/frest/templates/routes.txt88
-rw-r--r--src/frest/utils.py44
16 files changed, 0 insertions, 755 deletions
diff --git a/src/frest/auth/__init__.py b/src/frest/auth/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/src/frest/auth/__init__.py
+++ /dev/null
diff --git a/src/frest/auth/forms.py b/src/frest/auth/forms.py
deleted file mode 100644
index abc2f49..0000000
--- a/src/frest/auth/forms.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from .models import User
-from forms import ModelForm
-
-
-class UserForm(ModelForm):
- model = User
-
- def __init__(self, data):
- super().__init__(self.model)
- self.data = data
diff --git a/src/frest/auth/models.py b/src/frest/auth/models.py
deleted file mode 100644
index ea79def..0000000
--- a/src/frest/auth/models.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from database import db
-from datetime import datetime
-import string
-import random
-from hashlib import sha256
-from pytz import timezone
-import os
-
-
-def generate_token():
- chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
- return "".join(random.choice(chars) for _ in range(18))
-
-
-class User(db.Model):
- userId = db.Column(db.Integer, primary_key=True)
- email = db.Column(db.String(30))
- password = db.Column(db.String(30))
- is_admin = db.Column(db.Boolean, default=False)
- name = db.Column(db.String(30))
- created_at = db.Column(db.DateTime)
-
- def __init__(self, **kwargs):
- self.email = kwargs.get("email")
- psw_hash = sha256(kwargs.get("password").encode())
- self.password = psw_hash.hexdigest()
- self.name = kwargs.get("name")
- self.is_admin = kwargs.get("is_admin")
- self.created_at = datetime.now(
- timezone(os.getenv("FREST_TIMEZONE", "Europe/Rome"))
- )
-
- def __repr__(self):
- return f"<User '{self.userId}'>"
-
-
-class Token(db.Model):
- tokenId = db.Column(db.Integer, primary_key=True)
- string = db.Column(db.String(20))
- created_at = db.Column(db.DateTime)
- expired = db.Column(db.Boolean)
- user_id = db.Column(db.Integer, db.ForeignKey("user.userId"), nullable=False)
- user = db.relationship("User", backref=db.backref("tokens", lazy=True))
-
- def __init__(self, user):
- self.user = user
- self.string = f"{generate_token()}=="
- self.created_at = datetime.utcnow()
- self.expired = False
-
- def __repr__(self):
- return f"<Token '{self.string}'>"
diff --git a/src/frest/auth/routes.py b/src/frest/auth/routes.py
deleted file mode 100644
index 66bd8f5..0000000
--- a/src/frest/auth/routes.py
+++ /dev/null
@@ -1,183 +0,0 @@
-from flask import Blueprint, request, abort
-from utils import http_call, model_serialize
-from decorators import check_token, admin_required
-from .models import User, Token
-from .forms import UserForm
-from database import db
-from hashlib import sha256
-from sqlalchemy import desc
-
-api = Blueprint("users", __name__)
-
-
-@api.route("/api/login", methods=["POST"])
-def login():
- if not request.json:
- abort(400)
-
- data = request.json
-
- auth = request.headers.get("Authorization")
- if auth:
- t = Token.query.filter_by(string=auth).first()
- if not t:
- abort(404)
-
- if t.user.is_admin:
- return http_call(
- {"userId": t.user.userId, "login": True, "token": t.string}, 200
- )
- else:
- abort(403)
-
- if "email" in data and "password" in data:
- psw_hash = sha256(data["password"].encode())
- data["password"] = psw_hash.hexdigest()
- u = User.query.filter_by(email=data["email"], password=data["password"]).first()
-
- if not u:
- abort(404)
-
- if "is_admin" in data:
- if u.is_admin == 0:
- abort(403)
-
- last_token = (
- Token.query.filter_by(user=u).order_by(desc(Token.tokenId)).all()[-1]
- )
- last_token.expired = True
-
- t = Token(user=u)
-
- db.session.add(t)
- db.session.commit()
-
- return http_call({"userId": u.userId, "login": True, "token": t.string}, 200)
-
- abort(404)
-
-
-@api.route("/api/user/hash_password", methods=["GET"])
-def hash_password_exists():
- data = request.args
- if not data.get("hash_password"):
- abort(400)
-
- if User.query.filter_by(password=data["hash_password"]):
- return http_call({}, 200)
-
- return http_call({}, 404)
-
-
-@api.route("/api/user/new-password/<alias>", methods=["PUT"])
-def new_user_password(alias):
- data = request.json
- if not data.get("password"):
- abort(400)
-
- u = User.query.filter_by(password=alias).first()
-
- if not u:
- abort(404)
-
- u.password = sha256(data["password"].encode()).hexdigest()
- db.session.commit()
-
- return http_call({}, 200)
-
-
-@api.route("/api/user", methods=["POST"])
-def new_user():
- if not request.json:
- abort(400)
-
- form = UserForm(request.json)
-
- if not form.get("is_admin") or form.is_valid():
- if User.query.filter_by(email=form.get("email")).first():
- abort(400)
-
- u = User(
- email=form.get("email"),
- password=form.get("password"),
- name=form.get("name"),
- is_admin=form.get("is_admin"),
- )
- t = Token(user=u)
- db.session.add(u)
- db.session.add(t)
-
- db.session.commit()
-
- return http_call({"userId": u.userId, "token": t.string}, 201)
-
- abort(400)
-
-
-@api.route("/api/users")
-@check_token
-@admin_required
-def all_users():
- return http_call(
- [
- model_serialize(i, params="userId,email,is_admin,name,created_at")
- for i in User.query.all()
- ],
- 200,
- )
-
-
-@api.route("/api/user/<int:userId>")
-@check_token
-def get_user(userId):
- return http_call(
- model_serialize(
- User.query.filter_by(userId=userId).first(),
- params="userId,email,is_admin,name,created_at",
- ),
- 200,
- )
-
-
-@api.route("/api/user/<userId>", methods=["DELETE"])
-@check_token
-def delete_user(userId):
- u = User.query.filter_by(userId=userId)
- if not u:
- abort(404)
-
- deleted = u.delete()
- db.session.commit()
-
- return http_call({"delete": deleted}, 200)
-
-
-@api.route("/api/user/<userId>", methods=["PUT"])
-@check_token
-def edit_user(userId):
- if not request.json:
- abort(400)
-
- form = UserForm(request.json)
- u = User.query.filter_by(userId=userId).first()
- if not u:
- abort(400)
-
- if form.get("password"):
- psw = True
- else:
- psw = False
-
- if not psw or not form.get("is_admin") or form.is_valid():
- u.name = form.get("name")
- u.email = form.get("email")
- u.is_admin = form.get("is_admin")
- if psw:
- crypt_psw = sha256(form.get("password").encode()).hexdigest()
- u.password = crypt_psw
-
- db.session.commit()
-
- return http_call({"userId": u.userId}, 200)
-
- abort(400)
diff --git a/src/frest/database.py b/src/frest/database.py
deleted file mode 100644
index 176cd52..0000000
--- a/src/frest/database.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from flask_sqlalchemy import SQLAlchemy
-
-db = SQLAlchemy()
-config = {"DATABASE_URI": "sqlite:///database.db"}
diff --git a/src/frest/decorators.py b/src/frest/decorators.py
deleted file mode 100644
index 7ce79d7..0000000
--- a/src/frest/decorators.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from flask import request, abort
-from auth.models import Token
-from functools import wraps
-
-
-def check_token(f):
- @wraps(f)
- def inner(*args, **kwargs):
- userid = request.url.split("/")[-1]
- headers = request.headers
- if not headers.get("Authorization"):
- abort(403)
-
- auth = request.headers.get("Authorization")
- token = Token.query.filter_by(string=auth).first()
- if not token:
- abort(403)
-
- if userid.isdigit():
- if int(userid) != token.user.userId and not token.user.is_admin:
- abort(403)
-
- return f(*args, **kwargs)
-
- return inner
-
-
-def admin_required(f):
- @wraps(f)
- def inner(*args, **kwargs):
- header = request.headers
-
- auth = request.headers.get("Authorization")
- token = Token.query.filter_by(string=auth).first()
- if not token.user.is_admin:
- abort(403)
-
- return f(*args, **kwargs)
-
- return inner
diff --git a/src/frest/forms.py b/src/frest/forms.py
deleted file mode 100644
index 8ce2e95..0000000
--- a/src/frest/forms.py
+++ /dev/null
@@ -1,20 +0,0 @@
-class ModelForm(object):
- def __init__(self, model):
- self.data = {}
- self.model = model
- no_params = ["metadata", "query", "query_class"]
- self.attributes = [
- i for i in dir(self.model) if not i.startswith("_") and i not in no_params
- ]
- self.ignore = []
-
- def is_valid(self):
- for key, value in self.data.items():
- if key in self.attributes:
- if (value == "" or not value) and key not in self.ignore:
- return False
-
- return True
-
- def get(self, attr):
- return self.data.get(attr)
diff --git a/src/frest/mail.py b/src/frest/mail.py
deleted file mode 100644
index e553525..0000000
--- a/src/frest/mail.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from flask_mail import Mail
-
-mail = Mail()
-config = {
- "SERVER": "",
- "PORT": 587,
- "USE_TLS": True,
- "USERNAME": "",
- "DEFAULT_SENDER": "",
- "PASSWORD": "",
-}
diff --git a/src/frest/manage.py b/src/frest/manage.py
deleted file mode 100644
index 1164d70..0000000
--- a/src/frest/manage.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import argparse
-from manage.utils import logo, create_app
-from manage.utils import logging, logging_arg
-import os
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("--startapp", "-s", type=str, help="create new app")
- args = parser.parse_args()
-
- if args.startapp:
- if not os.path.exists("scheme"):
- logging_arg("Create {}... ", "scheme/")
- logging("OK", 3, "\n")
- os.mkdir("scheme")
-
- create_app(args.startapp)
-
-
-if __name__ == "__main__":
- logo()
- main()
diff --git a/src/frest/manage/__init__.py b/src/frest/manage/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/src/frest/manage/__init__.py
+++ /dev/null
diff --git a/src/frest/manage/bcolors.py b/src/frest/manage/bcolors.py
deleted file mode 100644
index eb8b803..0000000
--- a/src/frest/manage/bcolors.py
+++ /dev/null
@@ -1,17 +0,0 @@
-class bcolors(object):
- DARK_GREY = "\033[90m"
- BOLD = "\033[1m"
- ERROR = "\033[91m"
- OK = "\033[92m"
- WARNING = "\033[93m"
- ENDC = "\033[0m"
-
-
-COLORS = [
- bcolors.DARK_GREY,
- bcolors.BOLD,
- bcolors.ERROR,
- bcolors.OK,
- bcolors.WARNING,
- bcolors.ENDC,
-]
diff --git a/src/frest/manage/utils.py b/src/frest/manage/utils.py
deleted file mode 100644
index f8e76d1..0000000
--- a/src/frest/manage/utils.py
+++ /dev/null
@@ -1,226 +0,0 @@
-import os
-from .bcolors import COLORS
-
-
-ENDC = len(COLORS) - 1
-
-
-def logging(text, _type=ENDC, end=""):
- print(f"{COLORS[_type]}{text}{COLORS[ENDC]}", end=end)
-
-
-def logging_arg(text, *args):
- args = [f"{COLORS[0]}{i}{COLORS[ENDC]}" for i in args]
- print(text.format(*args), end="")
-
-
-def logo():
- print(
- """
- __ _
- / _| | |
- | |_ _ __ ___ ___| |_
- | _| '__/ _ \/ __| __|
- | | | | | __/\__ \ |_
- |_| |_| \___||___/\__|
- \n\n"""
- )
-
-
-def inputsr():
- return input().strip().replace(" ", "_").lower()
-
-
-def create_field(fields):
- field = {"name": "", "type": "", "nullable": True}
- logging("Choose field name: ")
- field_name = inputsr()
- while (len(field_name) < 1 or field_name[0].isdigit()) or (
- field_name in fields or field_name in ["id", "created_at", "updated_at"]
- ):
- if len(field_name) < 1 or field_name[0].isdigit():
- logging("Field name must not be empty or starts with a number", 2, "\n")
- else:
- logging("Field name already exists", 2, "\n")
-
- logging("Choose field name: ")
- field_name = inputsr()
-
- field["name"] = field_name
-
- logging("Choose field type: ")
- logging("int, str, text, datetime, float, bool", 0, " ")
- field_type = inputsr()
- if field_type not in ["int", "str", "text", "datetime", "float", "bool"]:
- logging("Field type must be one of the supported type", 2, "\n")
- logging("Choose field type: ")
- logging("int, str, text, datetime, float, bool", 0, " ")
- field_type = inputsr()
-
- if field_type == "str":
- logging("Choose string size: ")
- stringsize = inputsr()
- while not stringsize.isdigit():
- logging("String size must be an integer number", 2, "\n")
- logging("Choose string size: ")
- stringsize = inputsr()
-
- stringsize = int(stringsize)
- if stringsize < 1:
- logging("You inserted 0, so it will be 1 by default", 4, "\n")
- stringsize = 1
-
- field["size"] = stringsize
-
- field["type"] = field_type
-
- logging("Field is nullable? (Y/n): ")
- field_nullable = inputsr()
-
- if len(field_nullable) > 0:
- while True:
- if field_nullable[0] not in ["y", "n"]:
- logging("Field is nullable? (Y/n): ")
- field_nullable = inputsr()
- else:
- break
-
- if field_nullable[0] == "n":
- field["nullable"] = False
-
- return field
-
-
-def create_model_cli(name):
- logging(
- "Fields id, created_at, updated_at are default on every new model, you can delete it from model file",
- 4,
- "\n\n",
- )
- fields = []
- logging("Create field? (Y/n): ")
- answer = inputsr()
-
- while answer in ["y", "", "yes"]:
- field = create_field(fields)
- fields.append(field)
-
- logging("Create new field? (Y/n): ")
- answer = inputsr()
-
- fields_string = ""
- init_fields_s = ""
- for field in fields:
- field_string = f"{field['name']} = db.Column("
- if field["type"] == "int":
- field_string += "db.Integer"
- elif field["type"] == "str":
- field_string += f"db.String({field['size']})"
- elif field["type"] == "text":
- field_string += "db.Text"
- elif field["type"] == "datetime":
- field_string += "db.Datetime"
- elif field["type"] == "float":
- field_string += "db.Float"
- else:
- field_string += "db.Boolean"
-
- field_string += f", nullable={field['nullable']})\n\t".replace("\t", " " * 4)
-
- fields_string += field_string
- init_fields_s += f"self.{field['name']} = kwargs.get('{field['name']}')\n\t\t".replace(
- "'", '"'
- ).replace(
- "\t", " " * 4
- )
-
- with open("templates/models.txt") as f:
- modeltext = "".join(f.readlines())
-
- modeltext = modeltext.replace("%%NAME%%", name.capitalize())
- modeltext = modeltext.replace("%%name%%", name)
- modeltext = modeltext.replace("%%params_model%%", fields_string[:-5])
- modeltext = modeltext.replace("%%params_model_init%%", init_fields_s[:-9])
-
- modelpath = f"scheme/{name}/models.py"
- logging_arg("Create {}... ", modelpath)
- with open(modelpath, "w") as f:
- f.write(modeltext)
-
- return fields
-
-
-def create_forms(name):
- with open("templates/form.txt") as f:
- formstext = "".join(f.readlines())
-
- formstext = formstext.replace("%%NAME%%", name.capitalize())
- with open(f"scheme/{name}/forms.py", "w") as f:
- f.write(formstext)
-
-
-def create_routes(name, fields):
- fields_l = [f"{name}Id"]
- fields_l.extend([i["name"] for i in fields])
- fields_l.extend(["created_at", "updated_at"])
-
- params_form = ""
- for i in fields:
- params_form += f"{i['name']}=form.get('{i['name']}'),\n\t\t\t".replace(
- "'", '"'
- ).replace("\t", " " * 4)
-
- params_put = ""
- for i in fields:
- params_put += f"{name[0]}.{i['name']} = form.get('{i['name']}')\n\t\t".replace(
- "'", '"'
- ).replace("\t", " " * 4)
-
- with open("templates/routes.txt") as f:
- routestext = "".join(f.readlines())
-
- routestext = routestext.replace("%%NAME%%", name.capitalize())
- routestext = routestext.replace("%%name%%", name)
- routestext = routestext.replace("%%params%%", ",".join(fields_l))
- routestext = routestext.replace("%%first_char%%", name[0])
- routestext = routestext.replace("%%params_form%%", params_form[:-13])
- routestext = routestext.replace("%%params_put%%", params_put[:-9])
-
- with open(f"scheme/{name}/routes.py", "w") as f:
- f.write(routestext)
-
-
-def create_app(name):
- name = name.lower().replace("-", "_")
-
- if name.isdigit() or name[0].isdigit():
- logging("Name cannot be a number o starts with a number", 2)
- return
-
- if len(name) < 2:
- logging("Name of app must be minimun 2 characters long", 2)
- return
-
- if os.path.exists(f"scheme/{name}"):
- logging("App already exists", 2)
- return
-
- logging_arg("Create {}... ", f"scheme/{name}")
- os.mkdir(f"scheme/{name}")
- logging("OK", 3, "\n")
-
- logging_arg("Create {}... ", f"scheme/{name}/__init__.py")
- open(f"scheme/{name}/__init__.py", "w").close()
- logging("OK", 3, "\n")
-
- logging_arg("Create model for {}...\n", name)
- fields = create_model_cli(name)
- logging("OK", 3, "\n")
-
- logging_arg("Create {}... ", f"scheme/{name}/forms.py")
- create_forms(name)
- logging("OK", 3, "\n")
-
- logging_arg("Create {}... ", f"scheme/{name}/routes.py")
- create_routes(name, fields)
- logging("OK", 3, "\n")
diff --git a/src/frest/templates/form.txt b/src/frest/templates/form.txt
deleted file mode 100644
index ed1017e..0000000
--- a/src/frest/templates/form.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-from .models import %%NAME%%
-from src.forms import ModelForm
-
-
-class %%NAME%%Form(ModelForm):
- model = %%NAME%%
-
- def __init__(self, data):
- super().__init__(self.model)
- self.data = data
- self.ignore = []
diff --git a/src/frest/templates/models.txt b/src/frest/templates/models.txt
deleted file mode 100644
index af80963..0000000
--- a/src/frest/templates/models.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-from database import db
-from datetime import datetime
-from pytz import timezone
-import os
-
-
-class %%NAME%%(db.Model):
- %%name%%Id = db.Column(db.Integer, primary_key=True)
- %%params_model%%
- created_at = db.Column(db.DateTime)
- updated_at = db.Column(db.DateTime)
-
- def __init__(self, **kwargs):
- %%params_model_init%%
- self.created_at = datetime.now(
- timezone(os.getenv("FREST_TIMEZONE", "Europe/Rome"))
- )
- self.updated_at = datetime.now(
- timezone(os.getenv("FREST_TIMEZONE", "Europe/Rome"))
- )
-
- def __repr__(self):
- return f"<%%NAME%% '{self.%%name%%Id}'>"
-
- def __str__(self):
- return f"{self.%%name%%Id}"
diff --git a/src/frest/templates/routes.txt b/src/frest/templates/routes.txt
deleted file mode 100644
index 11ba63a..0000000
--- a/src/frest/templates/routes.txt
+++ /dev/null
@@ -1,88 +0,0 @@
-from flask import Blueprint, request, abort
-from frest.utils import http_call, model_serialize
-from frest.decorators import check_token, admin_required
-from .models import %%NAME%%
-from .forms import %%NAME%%Form
-from database import db
-import json
-from datetime import datetime
-from pytz import timezone
-
-api = Blueprint("%%name%%s", __name__)
-
-
-@api.route("/api/%%name%%")
-def all_%%name%%s():
- return http_call(
- [
- model_serialize(i, params="%%params%%")
- for i in %%NAME%%.query.all()
- ],
- 200,
- )
-
-
-@api.route("/api/%%name%%/<%%name%%Id>")
-def get_%%name%%(%%name%%Id):
- %%first_char%% = %%NAME%%.query.filter_by(%%name%%Id=%%name%%Id).first()
- if not %%first_char%%:
- abort(404)
-
- return http_call(model_serialize(%%first_char%%, params="%%params%%"), 200)
-
-
-@api.route("/api/%%name%%/<%%name%%Id>", methods=["DELETE"])
-@check_token
-def delete_%%name%%(%%name%%Id):
- %%first_char%% = %%NAME%%.query.filter_by(%%name%%Id=%%name%%Id)
- if not %%first_char%%:
- abort(404)
-
- deleted = %%first_char%%.delete()
- db.session.commit()
-
- return http_call({"delete": deleted}, 200)
-
-
-@api.route("/api/%%name%%", methods=["POST"])
-@check_token
-def new_%%name%%():
- if not request.json:
- abort(400)
-
- form = %%NAME%%Form(request.json)
-
- if form.is_valid():
- %%first_char%% = %%NAME%%(
- %%params_form%%
- )
- db.session.add(%%first_char%%)
-
- db.session.commit()
-
- return http_call({"%%name%%Id": %%first_char%%.%%name%%Id}, 201)
-
- abort(400)
-
-
-@api.route("/api/%%name%%/<%%name%%Id>", methods=["PUT"])
-@check_token
-def edit_%%name%%(%%name%%Id):
- if not request.json:
- abort(400)
-
- form = %%NAME%%Form(request.json)
-
- if form.is_valid():
- %%first_char%% = %%NAME%%.query.filter_by(%%name%%Id=%%name%%Id).first()
- if not %%first_char%%:
- abort(404)
-
- %%params_put%%
- %%first_char%%.updated_at = datetime.now(timezone("Europe/Rome"))
-
- db.session.commit()
-
- return http_call({"%%name%%Id": %%first_char%%.%%name%%Id}, 200)
-
- abort(400)
diff --git a/src/frest/utils.py b/src/frest/utils.py
deleted file mode 100644
index 934aec4..0000000
--- a/src/frest/utils.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from flask import make_response, jsonify, request, render_template
-from flask_mail import Message
-from mail import mail
-from datetime import datetime
-import os
-
-
-def send_email(sender, email, activation_code, title, template):
- msg = Message(title, sender=sender, recipients=[email])
- rest_link = os.getenv("FREST_URL", "http://localhost:8080/app")
- msg.html = render_template(template, link=rest_link, code=activation_code)
- mail.send(msg)
-
-
-def http_call(data, status):
- return make_response(jsonify({"status": status, "result": data}), status)
-
-
-def model_serialize(obj, params="", extend_model_for=[]):
- fields = {}
- params = params.split(",")
-
- for i in [f for f in dir(obj) if f in params]:
- if isinstance(obj.__getattribute__(i), datetime):
- fields[i] = obj.__getattribute__(i).strftime("%d/%m/%Y %H:%M")
- else:
- fields[i] = obj.__getattribute__(i)
-
- if len(extend_model_for) > 0:
- for key, value in fields.items():
- if isinstance(value, list):
- _l = []
- for v in value:
- for i in extend_model_for:
- if isinstance(v, i):
- _l.append(v.as_json())
-
- fields[key] = _l
- else:
- for i in extend_model_for:
- if isinstance(value, i):
- fields[key] = value.as_json()
-
- return fields