Commit 78ce803f authored by Grégor JOUET's avatar Grégor JOUET 🔧
Browse files

Print management

parent 8a74e8e9
Loading
Loading
Loading
Loading

.vscode/settings.json

0 → 100644
+3 −0
Original line number Diff line number Diff line
{
    "python.linting.enabled": true
}
 No newline at end of file
+16 −5
Original line number Diff line number Diff line
@@ -4,17 +4,28 @@ Fablab 3d printing request application

## Minimum Viable Version

- [ ] Tests
- [x] User Connection
    - [ ] User administration
        - [ ] List users,
        - [ ] Change access level
- [ ] Submit a print request
    - [ ] Upload a STL file
    - [ ] Create a new Print Request
- [ ] Accept/Reject print request
    - [ ] See STL file in 3d 
    - [ ] See print status
    - [x] Upload a STL file
        - [x] Backend
        - [x] Front
    - [x] Create a new Print Request
        - [x] Backend
        - [x] Front
- [ ] Accept/Reject print request (front)
    - [x] See STL file in 3d 
    - [x] See print status
    - [ ] Change Print status (see enum in api)
- [ ] Slice 
    - [ ] Upload GCODE
    - [ ] **V2**: Open slicer window
- [ ] Message system
    - [ ] Post message on each request
    - [ ] New message indication
- [ ] **V2**: Send to printer
    - [ ] Working queues
    - [ ] Printer management
+131 −37
Original line number Diff line number Diff line
@@ -5,21 +5,25 @@ from flask_jwt_extended import (
    current_user
)

from myfab.model import *
import datetime
from myfab.model import Event, PrintRequest, User, init_db_connection, Queue
from flask_api import status
from flask import jsonify
import string
import random
import os
from flask_cors import CORS

init_db_connection()
log.info("Database connected")

app = Flask(__name__)
CORS(app)
app.debug = True
app.config['SECRET_KEY'] = 'super-secret' #! CHANGEME
app.config['JWT_IDENTITY_CLAIM'] = "login_name"

FILE_SAVE_PATH = "./files"
FILE_SAVE_PATH = "../ui/static/files"

jwt = JWTManager(app)

@@ -32,13 +36,14 @@ def identity(payload):

REQUEST_STATUS_PENDING_REVIEW = 0  # initial state
REQUEST_STATUS_APPROVED       = 1  # approved, waiting slicing
REQUEST_STATUS_AWAITING_PRINT = 2  # sliced, in print queur
REQUEST_STATUS_AWAITING_PRINT = 2  # sliced, in print queue
REQUEST_STATUS_PRINTING       = 3  # currently printing
REQUEST_STATUS_FINISHED       = 4  # done printing, waiting for recuperation
REQUEST_STATUS_CLOSED         = 5  # archived
REQUEST_STATUS_REJECTED       = 10 # refused by operator
REQUEST_STATUS_PRINT_FAILED   = 11 # print failed more than allowed
REQUEST_STATUS_STANDBY        = 12 # on hold, from any state
REQUEST_STATUS_CANCELED       = 13 # annulée

REQUEST_ACCESS_DENIED   = 0 # Not allowed to see this request
REQUEST_ACCESS_OBSERVER = 1 # Allowed to see this request
@@ -48,8 +53,7 @@ REQUEST_ACCESS_OPERATOR = 3 # Is an operator of the system
# Elements that can be modified by a user (not op)
REQUEST_USER_ELEMENTS = set(["title","description","project"])

### UTILS ####

#region UTILS 
def get_or_create_user(username: str, fullname: str = None):
    req = User.select().where(User.username == username)
    if(req.count()):
@@ -59,40 +63,70 @@ def get_or_create_user(username: str, fullname: str = None):
            raise Exception("fullname cannot be None")
        return User.create(username=username, fullname=fullname)
    
def lookup_requests(author: str = None, status: int = None):
def lookup_requests(author: str = None, status = None, req_id = None):
    where_req = []
    if author:
        where_req.append(PrintRequest.author.username == author)
    if status:
        where_req.append(PrintRequest.status == status)
    req = PrintRequest.select().where(*where_req).limit(30)

    return req.get()
    if status and len(status) > 0:
        w = None
        for sts in status:
            if w is None:
                w = PrintRequest.status == sts
            else:
                w = w or PrintRequest.status == sts
        where_req.append(w)

    if req_id:
        where_req.append(PrintRequest.id == req_id)

    req = PrintRequest.select().join(User, on=(PrintRequest.author == User.username)).limit(30) # .join(Queue, on=(PrintRequest.queue == Queue.id))
    if len(where_req) > 0:
        req = req.where(*where_req)

    req = req.order_by(PrintRequest.last_modification.desc())
    return req

def get_request_by_id(rid):
    req = PrintRequest.select.where(PrintRequest.id == rid)
    req = PrintRequest.select().where(PrintRequest.id == rid)
    return req.get() if req.count() else None

def add_request_event(req: PrintRequest, desc: str, op: User = None):
    log.info("Req %s: %s"%(req.id, desc))
    req.last_modification = datetime.datetime.now()
    req.save()
    Event.create(request = req, description = desc, operator = op)

def extract_date_time(elem):
    if type(elem) == tuple:
        elem = elem[0]
    # elem = datetime.datetime.fromisoformat(repr(elem))
    return f'{elem.day}/{elem.month}/{elem.year} {elem.hour}:{elem.minute}:{elem.second}'

def loop_requests(reqs):
    rqs = []
    # if type(reqs) != list:
    #     reqs = [reqs]
    for req in reqs:
        rqs.append({
            request_id: req.id,
            title: req.title,
            description: req.description,
            stl: req.stl,
            status: req.status,
            project: req.project,
            operator: req.operator.username if req.operator is not None else "",        
            queue: req.queue.name if req.queue is not None else "",
            recup: req.recup_id
            "request_id": req.id,
            "title": req.title,
            "creation": extract_date_time(req.creation),
            "last_modification": extract_date_time(req.last_modification),
            "author": req.author.fullname,
            "description": req.description,
            "stl": req.stl,
            "status": req.status,
            "reject_message": req.reject_message,
            "project": req.project,
            "gcode": req.gcode,
            "operator": req.operator.username if req.operator is not None else "",
            "queue": req.queue.name if req.queue is not None else "",
            "recup": req.recup_id
        })
    return rqs

def get_access_level_to_request(req, user):
def get_access_level_to_request(req: PrintRequest, user):
    #req is id or req same for user
    # TODO
    return REQUEST_ACCESS_OPERATOR # FIXME
@@ -102,7 +136,11 @@ def update_requires_operator_rights(update_json):
    s.discard(REQUEST_USER_ELEMENTS)
    return len(s) > 0

#### ROUTES ####
#endregion UTILS

#region routes

#region Project management & basic routes

@app.route("/whoami")
@jwt_required
@@ -114,7 +152,6 @@ def whoami():
def get_my_requests():
    return jsonify(loop_requests(lookup_requests(current_user.username)))


@app.route("/requests/<req_id>", methods=['PUT'])
@jwt_required
def update_request(req_id):
@@ -122,16 +159,24 @@ def update_request(req_id):
    # Operator can update all request parameters
    # 

    req = get_request_by_id(req_id)
    req: PrintRequest = get_request_by_id(req_id)
    changes = request.get_json()
    if not changes:
        return jsonify({'error': 'No modifications'}), status.HTTP_400_BAD_REQUEST
    if req is None:
        return jsonify({'error': 'No such request'}, status.HTTP_404_NOT_FOUND)
        return jsonify({'error': 'No such request'}), status.HTTP_404_NOT_FOUND

    required_access = REQUEST_ACCESS_OPERATOR if req.status > 0 and 
    required_access = REQUEST_ACCESS_OPERATOR if req.status > 0 or update_requires_operator_rights(changes) else REQUEST_ACCESS_USER

    if get_access_level_to_request(req, current_user) < REQUEST_ACCESS_OPERATOR:
        return jsonify({'error': 'Access denied'}, status.HTTP_401_UNAUTHORIZED)
        return jsonify({'error': 'Access denied'}), status.HTTP_401_UNAUTHORIZED
    # TODO access

    request.get_json()
    for e in changes:
        setattr(req, e, changes[e])
    req.last_modification = datetime.datetime.now()
    req.save()
    return jsonify({})

@app.route("/requests/new", methods=['POST'])
@jwt_required
@@ -152,7 +197,7 @@ def add_new_request():
    add_request_event(req, "Request created")

    return jsonify({
        request_id: req.id
        "request_id": req.id
    })

@app.route("/files/<kind>/upload/<req_id>", methods=['POST'])
@@ -161,8 +206,8 @@ def stl_upload(kind, req_id):
    if not kind in ['stl', 'gcode']:
        return jsonify({'error': 'Invalid file kind'}, status.HTTP_400_BAD_REQUEST)

    request: PrintRequest = get_request_by_id(int(req_id))
    if request == None:
    req: PrintRequest = get_request_by_id(int(req_id))
    if req == None:
        return jsonify({'error': 'No such print request'}, status.HTTP_404_NOT_FOUND)
    required_access = REQUEST_ACCESS_USER if kind == "stl" else REQUEST_ACCESS_OPERATOR    

@@ -175,21 +220,27 @@ def stl_upload(kind, req_id):
    name = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(64)])
    
    if kind == "stl":
        request.stl = name
        req.stl = name
    elif kind == "gcode":
        request.gcode = name
        req.gcode = name
        req.status = REQUEST_STATUS_AWAITING_PRINT
    else:
        return jsonify({'error': "Invalid file kind"}, status.HTTP_400_BAD_REQUEST)

    file.save(os.path.join(FILE_SAVE_PATH, name))
    request.save()
    add_request_event(request, f'Added {kind} file')
    req.save()
    add_request_event(req, f'Added {kind} file')

    return jsonify({'ok': True})

@app.route("/requests/<req_id>")
@jwt_required
def get_request_infos(req_id):
    return jsonify(loop_requests(lookup_requests(req_id=req_id))) # ! FIXME Don't get request if no access

@app.route("/requests/list", methods=['POST'])
@jwt_required
def get_requests():
def get_requests(): #! FIXME Don't get request if not access
    params = request.get_json()
    if not 'username' in params:
        params['username'] = None
@@ -197,6 +248,49 @@ def get_requests():
        params['status'] = None
    return jsonify(loop_requests(lookup_requests(params['username'], params['status'])))

#endregion

#region user management routes

#endregion user management

#region Printer manager routes

#endregion printer management

#region queues management

#endregion queues

#region Slicer manager routes

"""
GET /slicer/update
Authorization: Slicer key
{
    "slicer_id",
}

200 OK
{
    "print_id"
    "title",
    "operator_name"
    "upload_token"
}

-------
 
POST /slicer/usage
{
    ??? infos & crash status
}

"""

#endregion slicer

#endregion routes

# peewee usage

Loading