Polish db class diagram
This commit is contained in:
parent
aa45d1b904
commit
b9f989270d
240
docs/Makefile
240
docs/Makefile
|
@ -1,240 +1,20 @@
|
||||||
# Makefile for Sphinx documentation
|
# Minimal makefile for Sphinx documentation
|
||||||
#
|
#
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS =
|
SPHINXOPTS =
|
||||||
SPHINXBUILD = sphinx-build
|
SPHINXBUILD = sphinx-build
|
||||||
PAPER =
|
SPHINXPROJ = Devicehub
|
||||||
|
SOURCEDIR = .
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
|
||||||
# User-friendly check for sphinx-build
|
# Put it first so that "make" without argument is like "make help".
|
||||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
|
||||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Internal variables.
|
|
||||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
|
||||||
PAPEROPT_letter = -D latex_paper_size=letter
|
|
||||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
# the i18n builder cannot share the environment and doctrees with the others
|
|
||||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
|
|
||||||
.PHONY: help
|
|
||||||
help:
|
help:
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
@echo " html to make standalone HTML files"
|
|
||||||
@echo " fasthtml 'fast html': to make HTML files without regenerating the API"
|
|
||||||
@echo " dirhtml to make HTML files named index.html in directories"
|
|
||||||
@echo " singlehtml to make a single large HTML file"
|
|
||||||
@echo " pickle to make pickle files"
|
|
||||||
@echo " json to make JSON files"
|
|
||||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
|
||||||
@echo " qthelp to make HTML files and a qthelp project"
|
|
||||||
@echo " applehelp to make an Apple Help Book"
|
|
||||||
@echo " devhelp to make HTML files and a Devhelp project"
|
|
||||||
@echo " epub to make an epub"
|
|
||||||
@echo " epub3 to make an epub3"
|
|
||||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
|
||||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
|
||||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
|
||||||
@echo " text to make text files"
|
|
||||||
@echo " man to make manual pages"
|
|
||||||
@echo " texinfo to make Texinfo files"
|
|
||||||
@echo " info to make Texinfo files and run them through makeinfo"
|
|
||||||
@echo " gettext to make PO message catalogs"
|
|
||||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
|
||||||
@echo " xml to make Docutils-native XML files"
|
|
||||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
|
||||||
@echo " linkcheck to check all external links for integrity"
|
|
||||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
|
||||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
|
||||||
@echo " dummy to check syntax errors of document sources"
|
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: help Makefile
|
||||||
clean:
|
|
||||||
rm -rf $(BUILDDIR)/*
|
|
||||||
rm -rf modules
|
|
||||||
|
|
||||||
.PHONY: html
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
html:
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
sphinx-apidoc -f -l -o modules ../ereuse_devicehub
|
%: Makefile
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
|
||||||
|
|
||||||
.PHONY: fasthtml
|
|
||||||
fasthtml:
|
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
|
||||||
|
|
||||||
.PHONY: dirhtml
|
|
||||||
dirhtml:
|
|
||||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
|
||||||
|
|
||||||
.PHONY: singlehtml
|
|
||||||
singlehtml:
|
|
||||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
|
||||||
|
|
||||||
.PHONY: pickle
|
|
||||||
pickle:
|
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the pickle files."
|
|
||||||
|
|
||||||
.PHONY: json
|
|
||||||
json:
|
|
||||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the JSON files."
|
|
||||||
|
|
||||||
.PHONY: htmlhelp
|
|
||||||
htmlhelp:
|
|
||||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
|
||||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
|
||||||
|
|
||||||
.PHONY: qthelp
|
|
||||||
qthelp:
|
|
||||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
|
||||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
|
||||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DeviceHub.qhcp"
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DeviceHub.qhc"
|
|
||||||
|
|
||||||
.PHONY: applehelp
|
|
||||||
applehelp:
|
|
||||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
|
||||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
|
||||||
"~/Library/Documentation/Help or install it in your application" \
|
|
||||||
"bundle."
|
|
||||||
|
|
||||||
.PHONY: devhelp
|
|
||||||
devhelp:
|
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished."
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/DeviceHub"
|
|
||||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DeviceHub"
|
|
||||||
@echo "# devhelp"
|
|
||||||
|
|
||||||
.PHONY: epub
|
|
||||||
epub:
|
|
||||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
|
||||||
|
|
||||||
.PHONY: epub3
|
|
||||||
epub3:
|
|
||||||
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
|
|
||||||
|
|
||||||
.PHONY: latex
|
|
||||||
latex:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
|
||||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
|
||||||
"(use \`make latexpdf' here to do that automatically)."
|
|
||||||
|
|
||||||
.PHONY: latexpdf
|
|
||||||
latexpdf:
|
|
||||||
sphinx-apidoc -f -l -o . ../ereuse_devicehub
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through pdflatex..."
|
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
.PHONY: latexpdfja
|
|
||||||
latexpdfja:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
.PHONY: text
|
|
||||||
text:
|
|
||||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
|
||||||
|
|
||||||
.PHONY: man
|
|
||||||
man:
|
|
||||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
|
||||||
|
|
||||||
.PHONY: texinfo
|
|
||||||
texinfo:
|
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
|
||||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
|
||||||
"(use \`make info' here to do that automatically)."
|
|
||||||
|
|
||||||
.PHONY: info
|
|
||||||
info:
|
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
|
||||||
@echo "Running Texinfo files through makeinfo..."
|
|
||||||
make -C $(BUILDDIR)/texinfo info
|
|
||||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
|
||||||
|
|
||||||
.PHONY: gettext
|
|
||||||
gettext:
|
|
||||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
|
||||||
|
|
||||||
.PHONY: changes
|
|
||||||
changes:
|
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
|
||||||
@echo
|
|
||||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
|
||||||
|
|
||||||
.PHONY: linkcheck
|
|
||||||
linkcheck:
|
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
|
||||||
@echo
|
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
|
||||||
|
|
||||||
.PHONY: doctest
|
|
||||||
doctest:
|
|
||||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
|
||||||
@echo "Testing of doctests in the sources finished, look at the " \
|
|
||||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
||||||
|
|
||||||
.PHONY: coverage
|
|
||||||
coverage:
|
|
||||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
|
||||||
@echo "Testing of coverage in the sources finished, look at the " \
|
|
||||||
"results in $(BUILDDIR)/coverage/python.txt."
|
|
||||||
|
|
||||||
.PHONY: xml
|
|
||||||
xml:
|
|
||||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
|
||||||
|
|
||||||
.PHONY: pseudoxml
|
|
||||||
pseudoxml:
|
|
||||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
|
||||||
|
|
||||||
.PHONY: dummy
|
|
||||||
dummy:
|
|
||||||
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. Dummy builder generates no files."
|
|
72
docs/association-events.puml
Normal file
72
docs/association-events.puml
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
@startuml
|
||||||
|
ChangeAssociation <|-- Organize
|
||||||
|
ChangeAssociation <|-- Transfer
|
||||||
|
Organize <|-- Plan
|
||||||
|
Organize <|-- Allocate
|
||||||
|
Allocate <|-- Accept
|
||||||
|
Allocate <|-- Reject
|
||||||
|
Allocate <|-- Assign
|
||||||
|
Allocate <|-- Authorize
|
||||||
|
Plan <|-- Reserve
|
||||||
|
Plan <|-- Cancel
|
||||||
|
Transfer <|-- Receive
|
||||||
|
ChangeAssociation <|-- Trade
|
||||||
|
Trade <|-- Sell
|
||||||
|
Trade <|-- Donate
|
||||||
|
Trade <|-- Pay
|
||||||
|
Trade <|-- Rent
|
||||||
|
Trade <|-- DisposeProduct
|
||||||
|
|
||||||
|
class ChangeAssociation {
|
||||||
|
agent: who did it
|
||||||
|
}
|
||||||
|
|
||||||
|
class Receive {
|
||||||
|
sender
|
||||||
|
recipient
|
||||||
|
}
|
||||||
|
|
||||||
|
class Reserve {
|
||||||
|
reservee
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cancel {
|
||||||
|
reservee
|
||||||
|
}
|
||||||
|
|
||||||
|
class Trade {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Allocate {
|
||||||
|
purpose
|
||||||
|
}
|
||||||
|
|
||||||
|
class Sell {
|
||||||
|
buyer
|
||||||
|
}
|
||||||
|
|
||||||
|
class Donate {
|
||||||
|
recipient
|
||||||
|
}
|
||||||
|
|
||||||
|
class Pay {
|
||||||
|
purpose
|
||||||
|
recipient
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rent {
|
||||||
|
recipient
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Association <|-- PhysicalPossessor
|
||||||
|
Association <|-- TradeAssociation
|
||||||
|
TradeAssociation <|-- Usufructuary
|
||||||
|
TradeAssociation <|-- Ownership
|
||||||
|
|
||||||
|
Sell - TradeAssociation
|
||||||
|
Donate - TradeAssociation
|
||||||
|
Rent -- Usufructuary : Sure?
|
||||||
|
Receive - PhysicalPossessor
|
||||||
|
@enduml
|
254
docs/conf.py
254
docs/conf.py
|
@ -1,41 +1,47 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# DeviceHub documentation build configuration file, created by
|
# Configuration file for the Sphinx documentation builder.
|
||||||
# sphinx-quickstart on Mon Apr 18 16:40:20 2016.
|
|
||||||
#
|
#
|
||||||
# This file is execfile()d with the current directory set to its
|
# This file does only contain a selection of the most common options. For a
|
||||||
# containing dir.
|
# full list see the documentation:
|
||||||
#
|
# http://www.sphinx-doc.org/en/master/config
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
import os
|
# -- Path setup --------------------------------------------------------------
|
||||||
import sys
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
sys.path.insert(0, os.path.abspath('../ereuse_devicehub'))
|
#
|
||||||
|
# import os
|
||||||
|
# import sys
|
||||||
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
|
||||||
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
|
project = 'Devicehub'
|
||||||
|
copyright = '2018, eReuse.org team'
|
||||||
|
author = 'eReuse.org team'
|
||||||
|
|
||||||
|
# The short X.Y version
|
||||||
|
version = ''
|
||||||
|
# The full version, including alpha/beta/rc tags
|
||||||
|
release = '0.1'
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
needs_sphinx = '1.4.7'
|
#
|
||||||
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
# ones.
|
# ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.autodoc',
|
|
||||||
'sphinx.ext.intersphinx',
|
'sphinx.ext.intersphinx',
|
||||||
'sphinx.ext.todo',
|
'sphinx.ext.todo',
|
||||||
'sphinx.ext.viewcode',
|
'sphinx.ext.viewcode',
|
||||||
'sphinxcontrib.httpdomain',
|
'sphinxcontrib.plantuml'
|
||||||
'sphinx.ext.todo'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
@ -43,29 +49,13 @@ templates_path = ['_templates']
|
||||||
|
|
||||||
# The suffix(es) of source filenames.
|
# The suffix(es) of source filenames.
|
||||||
# You can specify multiple suffix as a list of string:
|
# You can specify multiple suffix as a list of string:
|
||||||
|
#
|
||||||
# source_suffix = ['.rst', '.md']
|
# source_suffix = ['.rst', '.md']
|
||||||
source_suffix = '.rst'
|
source_suffix = '.rst'
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
# source_encoding = 'utf-8-sig'
|
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = 'DeviceHub'
|
|
||||||
copyright = '2017, eReuse.org team'
|
|
||||||
author = 'eReuse.org team'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
# The short X.Y version.
|
|
||||||
version = '0.1'
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = '0.1'
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
#
|
#
|
||||||
|
@ -73,157 +63,65 @@ release = '0.1'
|
||||||
# Usually you set "language" from the command line for these cases.
|
# Usually you set "language" from the command line for these cases.
|
||||||
language = None
|
language = None
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
# today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
# today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
# This patterns also effect to html_static_path and html_extra_path
|
# This pattern also affects html_static_path and html_extra_path .
|
||||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
|
||||||
# documents.
|
|
||||||
# default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
# add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
add_module_names = False
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
# show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
# -- Options for HTML output -------------------------------------------------
|
||||||
# modindex_common_prefix = []
|
|
||||||
|
|
||||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
|
||||||
# keep_warnings = False
|
|
||||||
|
|
||||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
|
||||||
todo_include_todos = True
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
html_theme = 'alabaster'
|
html_theme = 'alabaster'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
|
#
|
||||||
# html_theme_options = {}
|
# html_theme_options = {}
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
# html_theme_path = []
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents.
|
|
||||||
# "<project> v<release> documentation" by default.
|
|
||||||
# html_title = 'DeviceHub v0.1'
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
# html_short_title = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
# html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to use as a favicon of
|
|
||||||
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
# html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
# Custom sidebar templates, must be a dictionary that maps document names
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
# to template names.
|
||||||
# directly to the root of the documentation.
|
#
|
||||||
# html_extra_path = []
|
# The default sidebars (for documents that don't match any pattern) are
|
||||||
|
# defined by theme itself. Builtin themes are using these templates by
|
||||||
# If not None, a 'Last updated on:' timestamp is inserted at every page
|
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||||
# bottom, using the given strftime format.
|
# 'searchbox.html']``.
|
||||||
# The empty string is equivalent to '%b %d, %Y'.
|
#
|
||||||
# html_last_updated_fmt = None
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
# html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
# html_sidebars = {}
|
# html_sidebars = {}
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
# html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# -- Options for HTMLHelp output ---------------------------------------------
|
||||||
# html_domain_indices = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
# html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
html_split_index = True
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
# html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
# html_show_sphinx = True
|
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
# html_show_copyright = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
# html_use_opensearch = ''
|
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
# html_file_suffix = None
|
|
||||||
|
|
||||||
# Language to be used for generating the HTML full-text search index.
|
|
||||||
# Sphinx supports the following languages:
|
|
||||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
|
|
||||||
# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
|
|
||||||
# html_search_language = 'en'
|
|
||||||
|
|
||||||
# A dictionary with options for the search language support, empty by default.
|
|
||||||
# 'ja' uses this config value.
|
|
||||||
# 'zh' user can custom change `jieba` dictionary path.
|
|
||||||
# html_search_options = {'type': 'default'}
|
|
||||||
|
|
||||||
# The name of a javascript file (relative to the configuration directory) that
|
|
||||||
# implements a search results scorer. If empty, the default will be used.
|
|
||||||
# html_search_scorer = 'scorer.js'
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'DeviceHubdoc'
|
htmlhelp_basename = 'Devicehubdoc'
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
# -- Options for LaTeX output ------------------------------------------------
|
||||||
|
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#
|
||||||
# 'papersize': 'letterpaper',
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#
|
||||||
# 'pointsize': '10pt',
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#
|
||||||
# 'preamble': '',
|
# 'preamble': '',
|
||||||
|
|
||||||
# Latex figure (float) alignment
|
# Latex figure (float) alignment
|
||||||
|
#
|
||||||
# 'figure_align': 'htbp',
|
# 'figure_align': 'htbp',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,70 +129,44 @@ latex_elements = {
|
||||||
# (source start file, target name, title,
|
# (source start file, target name, title,
|
||||||
# author, documentclass [howto, manual, or own class]).
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
(master_doc, 'DeviceHub.tex', 'DeviceHub Documentation',
|
(master_doc, 'Devicehub.tex', 'Devicehub Documentation',
|
||||||
'eReuse.org team', 'manual'),
|
'eReuse.org team', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# -- Options for manual page output ------------------------------------------
|
||||||
# the title page.
|
|
||||||
# latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
# latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
# latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
# latex_show_urls = False
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
# latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
# latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ---------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
(master_doc, 'devicehub', 'DeviceHub Documentation',
|
(master_doc, 'devicehub', 'Devicehub Documentation',
|
||||||
[author], 1)
|
[author], 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# -- Options for Texinfo output ----------------------------------------------
|
||||||
# man_show_urls = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
(master_doc, 'DeviceHub', 'DeviceHub Documentation',
|
(master_doc, 'Devicehub', 'Devicehub Documentation',
|
||||||
author, 'DeviceHub', 'One line description of project.',
|
author, 'Devicehub', 'One line description of project.',
|
||||||
'Miscellaneous'),
|
'Miscellaneous'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# -- Extension configuration -------------------------------------------------
|
||||||
# texinfo_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
# texinfo_domain_indices = True
|
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
|
||||||
# texinfo_show_urls = 'footnote'
|
|
||||||
|
|
||||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
|
||||||
# texinfo_no_detailmenu = False
|
|
||||||
|
|
||||||
|
# -- Options for intersphinx extension ---------------------------------------
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
intersphinx_mapping = {'https://docs.python.org/': None}
|
intersphinx_mapping = {'https://docs.python.org/': None}
|
||||||
|
|
||||||
autodoc_default_flags = ['members', 'private-members']
|
# -- Options for todo extension ----------------------------------------------
|
||||||
autodoc_member_order = 'bysource'
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = True
|
||||||
|
|
||||||
|
# Plantuml
|
||||||
|
plantuml_output_format = 'svg'
|
||||||
|
|
||||||
|
# favicon
|
||||||
|
html_favicon = 'img/favicon.ico'
|
||||||
|
|
5
docs/event-diagram.rst
Normal file
5
docs/event-diagram.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Event Diagram
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. uml:: events.puml
|
||||||
|
:width: 100%
|
101
docs/events.puml
Normal file
101
docs/events.puml
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
@startuml
|
||||||
|
abstract class Rate
|
||||||
|
abstract class Event
|
||||||
|
abstract class Test
|
||||||
|
abstract class Allocate
|
||||||
|
abstract class Transfer
|
||||||
|
abstract class Trade
|
||||||
|
abstract class EventWithOneDevice
|
||||||
|
abstract class EventWithMultipleDevices
|
||||||
|
abstract class Organize
|
||||||
|
abstract class Plan
|
||||||
|
abstract class Step
|
||||||
|
abstract class PhotoboxRate
|
||||||
|
|
||||||
|
|
||||||
|
package "Devices" {
|
||||||
|
abstract class Device
|
||||||
|
abstract class Component
|
||||||
|
Device <|- Component
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IndividualRate "1..*" -- "1..*" AggregateRate : ratings <
|
||||||
|
|
||||||
|
Event <|-- EventWithOneDevice
|
||||||
|
Event <|-- EventWithMultipleDevices
|
||||||
|
EventWithOneDevice <|--- Snapshot
|
||||||
|
EventWithOneDevice <|--- Install
|
||||||
|
EventWithOneDevice <|-- Rate
|
||||||
|
Rate <|-- AggregateRate
|
||||||
|
Rate <|- IndividualRate
|
||||||
|
IndividualRate <|- PhotoboxRate
|
||||||
|
IndividualRate <|-- WorkbenchRate
|
||||||
|
EventWithOneDevice <|-- Test
|
||||||
|
Test <|-- TestDataStorage
|
||||||
|
Test <|-- StressTest
|
||||||
|
EventWithOneDevice <|--- EraseBasic
|
||||||
|
EraseBasic <|- EraseSectors
|
||||||
|
|
||||||
|
Step <|-- StepZero
|
||||||
|
Step <|-- StepRandom
|
||||||
|
Snapshot "1" -- "1" SnapshotRequest
|
||||||
|
Event "*" -> "0..1" Snapshot : InSnapshot >
|
||||||
|
Event "*" -> "0..1" Component : affectedComponents >
|
||||||
|
Device "1" *-- "*" EventWithOneDevice : EventOn <
|
||||||
|
Device "1..*" *-- "1" EventWithMultipleDevices : EventOn <
|
||||||
|
EraseBasic "1" *-- "1..*" Step
|
||||||
|
PhotoboxRate <|-- PhotoboxSystemRate
|
||||||
|
PhotoboxRate <|-- PhotoboxPersonRate
|
||||||
|
|
||||||
|
package Images{
|
||||||
|
ImageList "1" *- "1..*" Image : In <
|
||||||
|
Device "1" *-- "*" ImageList
|
||||||
|
Image "1" *-- "*" PhotoboxRate
|
||||||
|
}
|
||||||
|
|
||||||
|
EventWithMultipleDevices <|- Organize
|
||||||
|
EventWithMultipleDevices <|-- Transfer
|
||||||
|
EventWithMultipleDevices <|-- Trade
|
||||||
|
EventWithMultipleDevices <|--- ToDispose
|
||||||
|
EventWithMultipleDevices <|--- Locate
|
||||||
|
EventWithMultipleDevices <|--- Migrate
|
||||||
|
EventWithMultipleDevices <|--- Prepare
|
||||||
|
EventWithMultipleDevices <|--- ReadyToUse
|
||||||
|
EventWithMultipleDevices <|--- Recycle
|
||||||
|
EventWithMultipleDevices <|--- Repair
|
||||||
|
EventWithMultipleDevices <|--- ToPrepare
|
||||||
|
EventWithMultipleDevices <|--- ToRepair
|
||||||
|
EventWithMultipleDevices <|--- DisposeWaste
|
||||||
|
EventWithMultipleDevices <|--- Recover
|
||||||
|
Transfer <|-- Receive
|
||||||
|
Trade <|-- Sell
|
||||||
|
Trade <|-- DisposeProduct
|
||||||
|
Trade <|-- Donate
|
||||||
|
Trade <|-- Pay
|
||||||
|
Trade <|-- Rent
|
||||||
|
Organize <|-- Allocate
|
||||||
|
Allocate <|-- Accept
|
||||||
|
Allocate <|-- Reject
|
||||||
|
Allocate <|-- Assign
|
||||||
|
Organize <|-- Plan
|
||||||
|
Plan <|-- Reserve
|
||||||
|
Plan <|-- CancelReservation
|
||||||
|
|
||||||
|
|
||||||
|
package Agents {
|
||||||
|
abstract class User
|
||||||
|
abstract class Agent
|
||||||
|
Event "*" -> "1" User : Author >
|
||||||
|
Event "*" - "0..1" Agent : agent >
|
||||||
|
|
||||||
|
Agent <|-- User
|
||||||
|
|
||||||
|
User <|-- Person
|
||||||
|
User <|-- System
|
||||||
|
Agent <|-- Organization
|
||||||
|
User "*" -o "0..1" Organization : WorksIn >
|
||||||
|
User "*" -o "0..1" Organization : activeOrganization >
|
||||||
|
}
|
||||||
|
|
||||||
|
@enduml
|
183
docs/events.rst
Normal file
183
docs/events.rst
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
Events
|
||||||
|
======
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
event-diagram
|
||||||
|
|
||||||
|
|
||||||
|
Rate
|
||||||
|
----
|
||||||
|
Devicehub generates an rating for a device taking into consideration the
|
||||||
|
visual, functional, and performance.
|
||||||
|
|
||||||
|
.. todo:: add performance as a result of component fusion + general tests in https://
|
||||||
|
github.com/eReuse/Rdevicescore/blob/master/img/input_process_output.png
|
||||||
|
|
||||||
|
A Workflow is as follows:
|
||||||
|
|
||||||
|
1. An agent generates feedback from the device in the form of benchmark,
|
||||||
|
visual, and functional information; which is filled in a ``Rate``
|
||||||
|
event. This is done through a **software**, defining the type
|
||||||
|
of ``Rate`` event. At the moment we have two rates: ``WorkbenchRate``
|
||||||
|
and ``PhotoboxRate``.
|
||||||
|
2. Devicehub gathers this information and computes a score that updates
|
||||||
|
the ``Rate`` event.
|
||||||
|
3. Devicehub aggregates different rates and computes a final score for
|
||||||
|
the device by performing a new ``AggregateRating`` event.
|
||||||
|
|
||||||
|
There are two **types** of ``Rate``: ``WorkbenchRate`` and
|
||||||
|
``PhotoboxRate``. Moreover, each rate can have different **versions**,
|
||||||
|
or different revisions of the algorithm used to compute the final score,
|
||||||
|
and Devicehub generates a rate event for **each** version. So, if
|
||||||
|
an agent fulfills a ``WorkbenchRate`` and there are 3 versions, Devicehub
|
||||||
|
generates 3 ``WorkbenchRate``. Devicehub understands that only one
|
||||||
|
version is the **official** and it will generate an ``AggregateRating``
|
||||||
|
only from the **official** version.
|
||||||
|
|
||||||
|
.. todo:: we should be able to disable a version without destroying code
|
||||||
|
|
||||||
|
In the future, Devicehub will be able to use different and independent
|
||||||
|
algorithms to calculate a ``Rate`` (not only changed by versions).
|
||||||
|
|
||||||
|
The technical Workflow in Devicehub is as follows:
|
||||||
|
|
||||||
|
1. In **T1**, the user performs a ``Snapshot`` by processing the device
|
||||||
|
through the Workbench. From the benchmarks and the visual and
|
||||||
|
functional ratings the user does in the device, the system generates
|
||||||
|
a ``WorkbenchRate``. With only this information,
|
||||||
|
the system generates an ``AggregateRating``, which is the event
|
||||||
|
that the user will see in the web.
|
||||||
|
2. In **T2**, the user takes pictures from the device through the
|
||||||
|
Photobox, and DeviceHub crates an ``ImageSet`` with multiple
|
||||||
|
``Image`` with information from the photobox.
|
||||||
|
3. In **T3**, an agent (user or AI) rates the pictures, creating a
|
||||||
|
``PhotoboxRate`` **for each** picture. When Devicehub receives the
|
||||||
|
first ``PhotoboxRate`` it creates an ``AggregateRating`` linked
|
||||||
|
to such ``PhotoboxRate``. So, the agent will perform as many
|
||||||
|
``PhotoboxRate`` as pictures are in the ``ImageSet``, and Devicehub
|
||||||
|
will link each ``PhotoboxRate`` to the same ``AggregateRating``.
|
||||||
|
This will end in **T3+Tn**, being *n* the number of photos to rate.
|
||||||
|
4. In **T3+Tn**, after the last photo is rated, Devicehub will generate
|
||||||
|
a new rate for the device: it takes the ``AggregateRating`` from 3.
|
||||||
|
and computes a rate from all the linked ``PhotoboxRate`` plus the
|
||||||
|
last available ``WorkbenchRate`` for that device.
|
||||||
|
|
||||||
|
If the agent in 3. is an user, Devicehub creates ``PhotoboxUserRate``
|
||||||
|
and if it is an AI it creates ``PhotoboxAIRate``.
|
||||||
|
|
||||||
|
The same ``ImageSet`` can be rated multiple times, generating a new
|
||||||
|
``AggregateRating`` each time.
|
||||||
|
|
||||||
|
.. todo:: which info does photobox provide for each picture?
|
||||||
|
|
||||||
|
Snapshot
|
||||||
|
--------
|
||||||
|
The Snapshot sets the physical information of the device (S/N, model...)
|
||||||
|
and updates it with erasures, benchmarks, ratings, and tests; updates the
|
||||||
|
composition of its components (adding / removing them), and links tags
|
||||||
|
to the device.
|
||||||
|
|
||||||
|
When receiving a Snapshot, the DeviceHub creates, adds and removes
|
||||||
|
components to match the Snapshot. For example, if a Snapshot of a computer
|
||||||
|
contains a new component, the system searches for the component in its
|
||||||
|
database and, if not found, its creates it; finally linking it to the
|
||||||
|
computer.
|
||||||
|
|
||||||
|
A Snapshot is used with Remove to represent changes in components for
|
||||||
|
a device:
|
||||||
|
|
||||||
|
1. ``Snapshot`` creates a device if it does not exist, and the same
|
||||||
|
for its components. This is all done in one ``Snapshot``.
|
||||||
|
2. If the device exists, it updates its component composition by
|
||||||
|
*adding* and *removing* them. If,
|
||||||
|
for example, this new Snasphot doesn't have a component, it means that
|
||||||
|
this component is not present anymore in the device, thus removing it
|
||||||
|
from it. Then we have that:
|
||||||
|
|
||||||
|
- Components that are added to the device: snapshot2.components -
|
||||||
|
snapshot1.components
|
||||||
|
- Components that are removed to the device: snapshot1.components -
|
||||||
|
snapshot2.components
|
||||||
|
|
||||||
|
When adding a component, there may be the case this component existed
|
||||||
|
before and it was inside another device. In such case, DeviceHub will
|
||||||
|
perform ``Remove`` on the old parent.
|
||||||
|
|
||||||
|
Snapshots from Workbench
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
When processing a device from the Workbench, this one performs a Snapshot
|
||||||
|
and then performs more events (like testings, benchmarking...).
|
||||||
|
|
||||||
|
The use case, which is represented in the ``test_workbench_phases``,
|
||||||
|
is as follows:
|
||||||
|
|
||||||
|
1. In **T1**, WorkbenchServer (as the middleware from Workbench and
|
||||||
|
Devicehub) submits:
|
||||||
|
|
||||||
|
- A ``Snapshot`` event with the required information to **synchronize**
|
||||||
|
and **rate** the device. This is:
|
||||||
|
|
||||||
|
- Identification information about the device and components
|
||||||
|
(S/N, model, physical characteristics...)
|
||||||
|
- Tags.
|
||||||
|
- Rate.
|
||||||
|
- Benchmarks.
|
||||||
|
- TestDataStorage.
|
||||||
|
- An ordered set of **expected events**, defining which are the next
|
||||||
|
events that Workbench will perform to the device in ideal
|
||||||
|
conditions (device doesn't fail, no Internet drop...).
|
||||||
|
|
||||||
|
Devicehub **syncs** the device with the database and perform the
|
||||||
|
``Benchmark``, the ``TestDataStorage``, and finally the ``Rate``.
|
||||||
|
This leaves the Snapshot **open** to wait for the next events
|
||||||
|
to come.
|
||||||
|
2. Assuming that we expect all events, in **T2**, WorkbenchServer
|
||||||
|
submits a ``StressTest`` with a ``snapshot`` field containing the
|
||||||
|
ID of the Snapshot in 1, and Devicehub links the event with such
|
||||||
|
``Snapshot``.
|
||||||
|
3. In **T3**, WorkbenchServer submits the ``Erase`` with the ``Snapshot``
|
||||||
|
and ``component`` IDs from 1, linking it to them. It repeats
|
||||||
|
this for all the erased data storage devices; **T2+Tn** being
|
||||||
|
*n* the erased data storage devices.
|
||||||
|
4. WorkbenchServer does like in 3. but for the event ``Install``,
|
||||||
|
finishing in **T2+Tn+Tx**, being *x* the number of data storage
|
||||||
|
devices with an OS installed into.
|
||||||
|
5. In **T2+Tn+Tx**, when all *expected events* have been performed,
|
||||||
|
Devicehub **closes** the ``Snapshot`` from 1.
|
||||||
|
|
||||||
|
Optionally, Devicehub understands receiving a ``Snapshot`` with all
|
||||||
|
the events the following way:
|
||||||
|
|
||||||
|
- ``Install`` embedded in a ``installation`` field in its respective
|
||||||
|
``DataStorage`` component in the ``Snapshot``.
|
||||||
|
- ``Erase`` embedded in ``erasure`` field in its respective
|
||||||
|
``DataStorage`` in the ``Snapshot``.
|
||||||
|
- ``StressTest`` in an ``events`` field in the ``Snapshot``.
|
||||||
|
|
||||||
|
|
||||||
|
ToDispose and DisposeProduct
|
||||||
|
----------------------------
|
||||||
|
There are four events for getting rid of devices:
|
||||||
|
|
||||||
|
- ``ToDispose``: The device is marked to be disposed.
|
||||||
|
- ``DisposeProduct``: The device has been disposed. This is a ``Trade``
|
||||||
|
event, which means that you can optionally ``DisposeProduct``
|
||||||
|
to someone.
|
||||||
|
- ``RecyclingCenter`` have two extra special events:
|
||||||
|
- ``DisposeWaste``: The device has been disposed in an unspecified
|
||||||
|
manner.
|
||||||
|
- ``Recover``: The device has been scrapped and its materials have
|
||||||
|
been recovered under a new product.
|
||||||
|
|
||||||
|
.. note:: For usability purposes, users might not directly perform
|
||||||
|
``Dispose``, but this could automatically be done when
|
||||||
|
performing ``ToDispose`` + ``Receive`` to a
|
||||||
|
``RecyclingCenter``.
|
||||||
|
|
||||||
|
.. todo:: Ensure that ``Dispose`` is a ``Trade`` event. An Org could
|
||||||
|
``Sell`` or ``Donate`` a device with the objective of
|
||||||
|
disposing them. Is ``Dispose`` ok, or do we want to keep
|
||||||
|
that extra ``Sell`` or ``Donate`` event? Could dispose
|
||||||
|
be a synonym of any of those?
|
BIN
docs/img/favicon.ico
Normal file
BIN
docs/img/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -1,20 +1,20 @@
|
||||||
.. Dependencies: sphinx sphinxcontrib-httpdomain
|
.. title:: DeviceHub
|
||||||
.. title:: DeviceHub
|
|
||||||
|
|
||||||
.. image:: https://www.ereuse.org/files/2017/04/DeviceHub-logo-V2.svg
|
.. image:: https://www.ereuse.org/files/2017/04/DeviceHub-logo-V2.svg
|
||||||
:height: 100px
|
:height: 100px
|
||||||
:alt: DeviceHub logo
|
:alt: DeviceHub logo
|
||||||
|
|
||||||
This is the documentation and API of the
|
|
||||||
`eReuse.org DeviceHub <https://github.com/eReuse/DeviceHub>`_.
|
This is the documentation and API of the `eReuse.org DeviceHub
|
||||||
|
<https://github.com/eReuse/DeviceHub>`_.
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
snapshot
|
events
|
||||||
|
tags
|
||||||
|
|
||||||
* :ref:`genindex`
|
* :ref:`genindex`
|
||||||
* :ref:`modindex`
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
||||||
.. image::
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
Snapshot
|
|
||||||
========
|
|
||||||
The Snapshot updates the state of the device with information about its components and events
|
|
||||||
performed at them.
|
|
||||||
|
|
||||||
When receiving a Snapshot, the DeviceHub creates, adds and removes components to match the
|
|
||||||
Snapshot. For example, if a Snapshot of a computer contains a new component, the system will
|
|
||||||
search for the component in its database and, if not found, create it, and finally adding it
|
|
||||||
to the computer.
|
|
||||||
|
|
||||||
Snapshots can bundle some events, usually tests and hard-drive erasures. In such case the
|
|
||||||
DeviceHub will save those events.
|
|
||||||
|
|
||||||
A Snapshot is used with Remove to represent changes in components for a device:
|
|
||||||
1. A device is created in the database always with a Snapshot. If this device had components,
|
|
||||||
they are created (if they did not existed before) in the same time with the same Snapshot.
|
|
||||||
2. Time after, a new Snapshot updates component information. If, for example, this new Snasphot
|
|
||||||
doesn't have a component, it means that this component is not present anymore in the device,
|
|
||||||
thus removing it from it. Then we have that:
|
|
||||||
- Components to add: snapshot2.components - snapshot1.components
|
|
||||||
- Components to remove: snapshot1.components - snapshot2.components
|
|
||||||
When adding a component, there may be the case this component existed before and it was
|
|
||||||
inside another device. In such case, DeviceHub will perform ``Remove`` on the old parent.
|
|
|
@ -1,11 +1,11 @@
|
||||||
from typing import Any, Iterable, Tuple, Type, Union
|
from typing import Any, Dict, Iterable, Tuple, Type, Union, Generator
|
||||||
|
|
||||||
from boltons.typeutils import issubclass
|
from boltons.typeutils import issubclass
|
||||||
from ereuse_utils.test import JSON
|
|
||||||
from flask import Response
|
from flask import Response
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
from ereuse_devicehub.resources.models import Thing
|
from ereuse_devicehub.resources.models import Thing
|
||||||
|
from ereuse_utils.test import JSON
|
||||||
from teal.client import Client as TealClient
|
from teal.client import Client as TealClient
|
||||||
from teal.marshmallow import ValidationError
|
from teal.marshmallow import ValidationError
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class Client(TealClient):
|
||||||
|
|
||||||
def open(self,
|
def open(self,
|
||||||
uri: str,
|
uri: str,
|
||||||
res: str or Type[Thing] = None,
|
res: Union[str, Type[Thing]] = None,
|
||||||
status: Union[int, Type[HTTPException], Type[ValidationError]] = 200,
|
status: Union[int, Type[HTTPException], Type[ValidationError]] = 200,
|
||||||
query: Iterable[Tuple[str, Any]] = tuple(),
|
query: Iterable[Tuple[str, Any]] = tuple(),
|
||||||
accept=JSON,
|
accept=JSON,
|
||||||
|
@ -29,7 +29,7 @@ class Client(TealClient):
|
||||||
item=None,
|
item=None,
|
||||||
headers: dict = None,
|
headers: dict = None,
|
||||||
token: str = None,
|
token: str = None,
|
||||||
**kw) -> (dict or str, Response):
|
**kw) -> Tuple[Union[Dict[str, Any], str], Response]:
|
||||||
if issubclass(res, Thing):
|
if issubclass(res, Thing):
|
||||||
res = res.__name__
|
res = res.__name__
|
||||||
return super().open(uri, res, status, query, accept, content_type, item, headers, token,
|
return super().open(uri, res, status, query, accept, content_type, item, headers, token,
|
||||||
|
@ -44,7 +44,7 @@ class Client(TealClient):
|
||||||
accept: str = JSON,
|
accept: str = JSON,
|
||||||
headers: dict = None,
|
headers: dict = None,
|
||||||
token: str = None,
|
token: str = None,
|
||||||
**kw) -> (dict or str, Response):
|
**kw) -> Tuple[Union[Dict[str, Any], str], Response]:
|
||||||
return super().get(uri, res, query, status, item, accept, headers, token, **kw)
|
return super().get(uri, res, query, status, item, accept, headers, token, **kw)
|
||||||
|
|
||||||
def post(self,
|
def post(self,
|
||||||
|
@ -57,7 +57,7 @@ class Client(TealClient):
|
||||||
accept: str = JSON,
|
accept: str = JSON,
|
||||||
headers: dict = None,
|
headers: dict = None,
|
||||||
token: str = None,
|
token: str = None,
|
||||||
**kw) -> (dict or str, Response):
|
**kw) -> Tuple[Union[Dict[str, Any], str], Response]:
|
||||||
return super().post(data, uri, res, query, status, content_type, accept, headers, token,
|
return super().post(data, uri, res, query, status, content_type, accept, headers, token,
|
||||||
**kw)
|
**kw)
|
||||||
|
|
||||||
|
@ -66,6 +66,20 @@ class Client(TealClient):
|
||||||
assert isinstance(password, str)
|
assert isinstance(password, str)
|
||||||
return self.post({'email': email, 'password': password}, '/users/login', status=200)
|
return self.post({'email': email, 'password': password}, '/users/login', status=200)
|
||||||
|
|
||||||
|
def get_many(self,
|
||||||
|
res: Union[Type[Thing], str],
|
||||||
|
resources: Iterable[dict],
|
||||||
|
key: str = None,
|
||||||
|
headers: dict = None,
|
||||||
|
token: str = None,
|
||||||
|
accept: str = JSON,
|
||||||
|
**kw) -> Iterable[Union[Dict[str, Any], str]]:
|
||||||
|
"""Like :meth:`.get` but with many resources."""
|
||||||
|
return (
|
||||||
|
self.get(res=res, item=r['key'] if key else r, headers=headers, token=token, **kw)[0]
|
||||||
|
for r in resources
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserClient(Client):
|
class UserClient(Client):
|
||||||
"""
|
"""
|
||||||
|
@ -87,7 +101,7 @@ class UserClient(Client):
|
||||||
|
|
||||||
def open(self,
|
def open(self,
|
||||||
uri: str,
|
uri: str,
|
||||||
res: str = None,
|
res: Union[str, Type[Thing]] = None,
|
||||||
status: int or HTTPException = 200,
|
status: int or HTTPException = 200,
|
||||||
query: Iterable[Tuple[str, Any]] = tuple(),
|
query: Iterable[Tuple[str, Any]] = tuple(),
|
||||||
accept=JSON,
|
accept=JSON,
|
||||||
|
@ -95,6 +109,6 @@ class UserClient(Client):
|
||||||
item=None,
|
item=None,
|
||||||
headers: dict = None,
|
headers: dict = None,
|
||||||
token: str = None,
|
token: str = None,
|
||||||
**kw) -> (dict or str, Response):
|
**kw) -> Tuple[Union[Dict[str, Any], str], Response]:
|
||||||
return super().open(uri, res, status, query, accept, content_type, item, headers,
|
return super().open(uri, res, status, query, accept, content_type, item, headers,
|
||||||
self.user['token'] if self.user else token, **kw)
|
self.user['token'] if self.user else token, **kw)
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
from distutils.version import StrictVersion
|
from distutils.version import StrictVersion
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
from ereuse_devicehub.resources.device import ComponentDef, ComputerDef, DesktopDef, DeviceDef, \
|
from ereuse_devicehub.resources.device import ComponentDef, ComputerDef, DataStorageDef, \
|
||||||
GraphicCardDef, HardDriveDef, LaptopDef, MicrotowerDef, MotherboardDef, NetbookDef, \
|
DesktopDef, DeviceDef, GraphicCardDef, HardDriveDef, LaptopDef, MicrotowerDef, \
|
||||||
NetworkAdapterDef, ProcessorDef, RamModuleDef, ServerDef
|
MotherboardDef, NetbookDef, NetworkAdapterDef, ProcessorDef, RamModuleDef, ServerDef, \
|
||||||
from ereuse_devicehub.resources.event import AddDef, EventDef, RemoveDef, SnapshotDef, TestDef, \
|
SolidStateDriveDef
|
||||||
TestHardDriveDef
|
from ereuse_devicehub.resources.event import AddDef, AggregateRateDef, EventDef, InstallDef, \
|
||||||
|
PhotoboxSystemRateDef, PhotoboxUserDef, RateDef, RemoveDef, SnapshotDef, StepDef, \
|
||||||
|
StepRandomDef, StepZeroDef, TestDataStorageDef, TestDef, WorkbenchRateDef, EraseBasicDef, \
|
||||||
|
EraseSectorsDef
|
||||||
from ereuse_devicehub.resources.tag import TagDef
|
from ereuse_devicehub.resources.tag import TagDef
|
||||||
from ereuse_devicehub.resources.user import OrganizationDef, UserDef
|
from ereuse_devicehub.resources.user import OrganizationDef, UserDef
|
||||||
from teal.config import Config
|
from teal.config import Config
|
||||||
|
|
||||||
|
|
||||||
class DevicehubConfig(Config):
|
class DevicehubConfig(Config):
|
||||||
RESOURCE_DEFINITIONS = (
|
RESOURCE_DEFINITIONS = {
|
||||||
DeviceDef, ComputerDef, DesktopDef, LaptopDef, NetbookDef, ServerDef,
|
DeviceDef, ComputerDef, DesktopDef, LaptopDef, NetbookDef, ServerDef,
|
||||||
MicrotowerDef, ComponentDef, GraphicCardDef, HardDriveDef, MotherboardDef,
|
MicrotowerDef, ComponentDef, GraphicCardDef, DataStorageDef, SolidStateDriveDef,
|
||||||
NetworkAdapterDef, RamModuleDef, ProcessorDef, UserDef, OrganizationDef, TagDef, EventDef,
|
HardDriveDef, MotherboardDef, NetworkAdapterDef, RamModuleDef, ProcessorDef, UserDef,
|
||||||
AddDef, RemoveDef, SnapshotDef, TestDef, TestHardDriveDef
|
OrganizationDef, TagDef, EventDef, AddDef, RemoveDef, EraseBasicDef, EraseSectorsDef,
|
||||||
)
|
StepDef, StepZeroDef, StepRandomDef, RateDef, AggregateRateDef, WorkbenchRateDef,
|
||||||
PASSWORD_SCHEMES = {'pbkdf2_sha256'}
|
PhotoboxUserDef, PhotoboxSystemRateDef, InstallDef, SnapshotDef, TestDef,
|
||||||
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/dh-db1'
|
TestDataStorageDef, WorkbenchRateDef
|
||||||
MIN_WORKBENCH = StrictVersion('11.0')
|
}
|
||||||
|
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/dh-db1' # type: str
|
||||||
|
MIN_WORKBENCH = StrictVersion('11.0') # type: StrictVersion
|
||||||
"""
|
"""
|
||||||
The minimum version of eReuse.org Workbench that this Devicehub
|
The minimum algorithm_version of eReuse.org Workbench that this Devicehub
|
||||||
accepts. We recommend not changing this value.
|
accepts. We recommend not changing this value.
|
||||||
"""
|
"""
|
||||||
ORGANIZATION_NAME = None # type: str
|
ORGANIZATION_NAME = None # type: str
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from ereuse_devicehub.resources.device.schemas import Component, Computer, Desktop, Device, \
|
from ereuse_devicehub.resources.device.schemas import Component, Computer, DataStorage, Desktop, \
|
||||||
GraphicCard, HardDrive, Laptop, Microtower, Motherboard, Netbook, NetworkAdapter, Processor, \
|
Device, GraphicCard, HardDrive, Laptop, Microtower, Motherboard, Netbook, NetworkAdapter, \
|
||||||
RamModule, Server
|
Processor, RamModule, Server, SolidStateDrive
|
||||||
from ereuse_devicehub.resources.device.views import DeviceView
|
from ereuse_devicehub.resources.device.views import DeviceView
|
||||||
from teal.resource import Converters, Resource
|
from teal.resource import Converters, Resource
|
||||||
|
|
||||||
|
@ -44,10 +44,18 @@ class GraphicCardDef(ComponentDef):
|
||||||
SCHEMA = GraphicCard
|
SCHEMA = GraphicCard
|
||||||
|
|
||||||
|
|
||||||
class HardDriveDef(ComponentDef):
|
class DataStorageDef(ComponentDef):
|
||||||
|
SCHEMA = DataStorage
|
||||||
|
|
||||||
|
|
||||||
|
class HardDriveDef(DataStorageDef):
|
||||||
SCHEMA = HardDrive
|
SCHEMA = HardDrive
|
||||||
|
|
||||||
|
|
||||||
|
class SolidStateDriveDef(DataStorageDef):
|
||||||
|
SCHEMA = SolidStateDrive
|
||||||
|
|
||||||
|
|
||||||
class MotherboardDef(ComponentDef):
|
class MotherboardDef(ComponentDef):
|
||||||
SCHEMA = Motherboard
|
SCHEMA = Motherboard
|
||||||
|
|
||||||
|
|
|
@ -3,37 +3,38 @@ from itertools import chain
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from typing import Dict, Set
|
from typing import Dict, Set
|
||||||
|
|
||||||
from ereuse_utils.naming import Naming
|
|
||||||
from sqlalchemy import BigInteger, Column, Float, ForeignKey, Integer, Sequence, SmallInteger, \
|
from sqlalchemy import BigInteger, Column, Float, ForeignKey, Integer, Sequence, SmallInteger, \
|
||||||
Unicode, inspect
|
Unicode, inspect
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from sqlalchemy.orm import ColumnProperty, backref, relationship
|
from sqlalchemy.orm import ColumnProperty, backref, relationship
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
|
from sqlalchemy_utils import ColorType
|
||||||
|
|
||||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE, STR_SM_SIZE, Thing
|
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE, STR_SM_SIZE, Thing
|
||||||
|
from ereuse_utils.naming import Naming
|
||||||
from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, check_range
|
from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, check_range
|
||||||
|
|
||||||
|
|
||||||
class Device(Thing):
|
class Device(Thing):
|
||||||
id = Column(BigInteger, Sequence('device_seq'), primary_key=True) # type: int
|
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
||||||
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
||||||
hid = Column(Unicode(STR_BIG_SIZE), unique=True) # type: str
|
hid = Column(Unicode(STR_BIG_SIZE), unique=True)
|
||||||
pid = Column(Unicode(STR_SIZE)) # type: str
|
model = Column(Unicode(STR_BIG_SIZE))
|
||||||
gid = Column(Unicode(STR_SIZE)) # type: str
|
manufacturer = Column(Unicode(STR_SIZE))
|
||||||
model = Column(Unicode(STR_BIG_SIZE)) # type: str
|
serial_number = Column(Unicode(STR_SIZE))
|
||||||
manufacturer = Column(Unicode(STR_SIZE)) # type: str
|
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 3))
|
||||||
serial_number = Column(Unicode(STR_SIZE)) # type: str
|
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 3))
|
||||||
weight = Column(Float(precision=3, decimal_return_scale=3),
|
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 3))
|
||||||
check_range('weight', 0.1, 3)) # type: float
|
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 3))
|
||||||
width = Column(Float(precision=3, decimal_return_scale=3),
|
color = Column(ColorType)
|
||||||
check_range('width', 0.1, 3)) # type: float
|
|
||||||
height = Column(Float(precision=3, decimal_return_scale=3),
|
|
||||||
check_range('height', 0.1, 3)) # type: float
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def events(self) -> list:
|
def events(self) -> list:
|
||||||
"""All the events performed to the device."""
|
"""
|
||||||
return sorted(chain(self.events_multiple, self.events_one), key=attrgetter('id'))
|
All the events performed to the device,
|
||||||
|
ordered by ascending creation time.
|
||||||
|
"""
|
||||||
|
return sorted(chain(self.events_multiple, self.events_one), key=attrgetter('created'))
|
||||||
|
|
||||||
def __init__(self, *args, **kw) -> None:
|
def __init__(self, *args, **kw) -> None:
|
||||||
super().__init__(*args, **kw)
|
super().__init__(*args, **kw)
|
||||||
|
@ -79,7 +80,7 @@ class Device(Thing):
|
||||||
|
|
||||||
|
|
||||||
class Computer(Device):
|
class Computer(Device):
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) # type: int
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
class Desktop(Computer):
|
class Desktop(Computer):
|
||||||
|
@ -103,7 +104,7 @@ class Microtower(Computer):
|
||||||
|
|
||||||
|
|
||||||
class Component(Device):
|
class Component(Device):
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) # type: int
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
||||||
parent_id = Column(BigInteger, ForeignKey(Computer.id))
|
parent_id = Column(BigInteger, ForeignKey(Computer.id))
|
||||||
parent = relationship(Computer,
|
parent = relationship(Computer,
|
||||||
|
@ -145,23 +146,31 @@ class JoinedComponentTableMixin:
|
||||||
|
|
||||||
|
|
||||||
class GraphicCard(JoinedComponentTableMixin, Component):
|
class GraphicCard(JoinedComponentTableMixin, Component):
|
||||||
memory = Column(SmallInteger, check_range('memory', min=1, max=10000)) # type: int
|
memory = Column(SmallInteger, check_range('memory', min=1, max=10000))
|
||||||
|
|
||||||
|
|
||||||
class HardDrive(JoinedComponentTableMixin, Component):
|
class DataStorage(JoinedComponentTableMixin, Component):
|
||||||
size = Column(Integer, check_range('size', min=1, max=10 ** 8)) # type: int
|
size = Column(Integer, check_range('size', min=1, max=10 ** 8))
|
||||||
|
|
||||||
|
|
||||||
|
class HardDrive(DataStorage):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SolidStateDrive(DataStorage):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Motherboard(JoinedComponentTableMixin, Component):
|
class Motherboard(JoinedComponentTableMixin, Component):
|
||||||
slots = Column(SmallInteger, check_range('slots')) # type: int
|
slots = Column(SmallInteger, check_range('slots'))
|
||||||
usb = Column(SmallInteger, check_range('usb')) # type: int
|
usb = Column(SmallInteger, check_range('usb'))
|
||||||
firewire = Column(SmallInteger, check_range('firewire')) # type: int
|
firewire = Column(SmallInteger, check_range('firewire'))
|
||||||
serial = Column(SmallInteger, check_range('serial')) # type: int
|
serial = Column(SmallInteger, check_range('serial'))
|
||||||
pcmcia = Column(SmallInteger, check_range('pcmcia')) # type: int
|
pcmcia = Column(SmallInteger, check_range('pcmcia'))
|
||||||
|
|
||||||
|
|
||||||
class NetworkAdapter(JoinedComponentTableMixin, Component):
|
class NetworkAdapter(JoinedComponentTableMixin, Component):
|
||||||
speed = Column(SmallInteger, check_range('speed', min=10, max=10000)) # type: int
|
speed = Column(SmallInteger, check_range('speed', min=10, max=10000))
|
||||||
|
|
||||||
|
|
||||||
class Processor(JoinedComponentTableMixin, Component):
|
class Processor(JoinedComponentTableMixin, Component):
|
||||||
|
|
151
ereuse_devicehub/resources/device/models.pyi
Normal file
151
ereuse_devicehub/resources/device/models.pyi
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
from typing import Dict, List, Set
|
||||||
|
|
||||||
|
from colour import Color
|
||||||
|
from sqlalchemy import Column
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.event.models import Event, EventWithMultipleDevices, \
|
||||||
|
EventWithOneDevice
|
||||||
|
from ereuse_devicehub.resources.image.models import ImageList
|
||||||
|
from ereuse_devicehub.resources.models import Thing
|
||||||
|
from ereuse_devicehub.resources.tag import Tag
|
||||||
|
|
||||||
|
|
||||||
|
class Device(Thing):
|
||||||
|
id = ... # type: Column
|
||||||
|
type = ... # type: Column
|
||||||
|
hid = ... # type: Column
|
||||||
|
model = ... # type: Column
|
||||||
|
manufacturer = ... # type: Column
|
||||||
|
serial_number = ... # type: Column
|
||||||
|
weight = ... # type: Column
|
||||||
|
width = ... # type: Column
|
||||||
|
height = ... # type: Column
|
||||||
|
depth = ... # type: Column
|
||||||
|
color = ... # type: Column
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.id = ... # type: int
|
||||||
|
self.type = ... # type: str
|
||||||
|
self.hid = ... # type: str
|
||||||
|
self.model = ... # type: str
|
||||||
|
self.manufacturer = ... # type: str
|
||||||
|
self.serial_number = ... # type: str
|
||||||
|
self.weight = ... # type: float
|
||||||
|
self.width = ... # type:float
|
||||||
|
self.height = ... # type: float
|
||||||
|
self.depth = ... # type: float
|
||||||
|
self.color = ... # type: Color
|
||||||
|
self.events = ... # type: List[Event]
|
||||||
|
self.physical_properties = ... # type: Dict[str, object or None]
|
||||||
|
self.events_multiple = ... # type: Set[EventWithMultipleDevices]
|
||||||
|
self.events_one = ... # type: Set[EventWithOneDevice]
|
||||||
|
self.images = ... # type: ImageList
|
||||||
|
self.tags = ... # type: Set[Tag]
|
||||||
|
|
||||||
|
|
||||||
|
class Computer(Device):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.components = ... # type: Set[Component]
|
||||||
|
|
||||||
|
|
||||||
|
class Desktop(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Laptop(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Netbook(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Server(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Microtower(Computer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Component(Device):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.parent_id = ... # type: int
|
||||||
|
self.parent = ... # type: Computer
|
||||||
|
self.events_components = ... # type: Set[Event]
|
||||||
|
|
||||||
|
def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component':
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GraphicCard(Component):
|
||||||
|
memory = ... # type: Column
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.memory = ... # type: int
|
||||||
|
|
||||||
|
|
||||||
|
class DataStorage(Component):
|
||||||
|
size = ... # type: Column
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.size = ... # type: int
|
||||||
|
|
||||||
|
|
||||||
|
class HardDrive(DataStorage):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SolidStateDrive(DataStorage):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Motherboard(Component):
|
||||||
|
slots = ... # type: Column
|
||||||
|
usb = ... # type: Column
|
||||||
|
firewire = ... # type: Column
|
||||||
|
serial = ... # type: Column
|
||||||
|
pcmcia = ... # type: Column
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.slots = ... # type: int
|
||||||
|
self.usb = ... # type: int
|
||||||
|
self.firewire = ... # type: int
|
||||||
|
self.serial = ... # type: int
|
||||||
|
self.pcmcia = ... # type: int
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkAdapter(Component):
|
||||||
|
speed = ... # type: Column
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.speed = ... # type: int
|
||||||
|
|
||||||
|
|
||||||
|
class Processor(Component):
|
||||||
|
speed = ... # type: Column
|
||||||
|
cores = ... # type: Column
|
||||||
|
address = ... # type: Column
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.speed = ... # type: float
|
||||||
|
self.cores = ... # type: int
|
||||||
|
self.address = ... # type: int
|
||||||
|
|
||||||
|
|
||||||
|
class RamModule(Component):
|
||||||
|
size = ... # type: Column
|
||||||
|
speed = ... # type: Column
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.size = ... # type: int
|
||||||
|
self.speed = ... # type: float
|
|
@ -14,11 +14,6 @@ class Device(Thing):
|
||||||
description='The Hardware ID is the unique ID traceability systems '
|
description='The Hardware ID is the unique ID traceability systems '
|
||||||
'use to ID a device globally.')
|
'use to ID a device globally.')
|
||||||
tags = NestedOn('Tag', many=True, collection_class=OrderedSet)
|
tags = NestedOn('Tag', many=True, collection_class=OrderedSet)
|
||||||
pid = Str(description='The PID identifies a device under a circuit or platform.',
|
|
||||||
validate=Length(max=STR_SIZE))
|
|
||||||
gid = Str(description='The Giver ID links the device to the giver\'s (donor, seller)'
|
|
||||||
'internal inventory.',
|
|
||||||
validate=Length(max=STR_SIZE))
|
|
||||||
model = Str(validate=Length(max=STR_BIG_SIZE))
|
model = Str(validate=Length(max=STR_BIG_SIZE))
|
||||||
manufacturer = Str(validate=Length(max=STR_SIZE))
|
manufacturer = Str(validate=Length(max=STR_SIZE))
|
||||||
serial_number = Str(data_key='serialNumber')
|
serial_number = Str(data_key='serialNumber')
|
||||||
|
@ -70,7 +65,7 @@ class GraphicCard(Component):
|
||||||
description='The amount of memory of the Graphic Card in MB.')
|
description='The amount of memory of the Graphic Card in MB.')
|
||||||
|
|
||||||
|
|
||||||
class HardDrive(Component):
|
class DataStorage(Component):
|
||||||
size = Integer(validate=Range(0, 10 ** 8),
|
size = Integer(validate=Range(0, 10 ** 8),
|
||||||
unit=UnitCodes.mbyte,
|
unit=UnitCodes.mbyte,
|
||||||
description='The size of the hard-drive in MB.')
|
description='The size of the hard-drive in MB.')
|
||||||
|
@ -79,6 +74,14 @@ class HardDrive(Component):
|
||||||
benchmarks = NestedOn('BenchmarkHardDrive', load_only=True, many=True)
|
benchmarks = NestedOn('BenchmarkHardDrive', load_only=True, many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class HardDrive(DataStorage):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SolidStateDrive(DataStorage):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Motherboard(Component):
|
class Motherboard(Component):
|
||||||
slots = Integer(validate=Range(1, 20), description='PCI slots the motherboard has.')
|
slots = Integer(validate=Range(1, 20), description='PCI slots the motherboard has.')
|
||||||
usb = Integer(validate=Range(0, 20))
|
usb = Integer(validate=Range(0, 20))
|
||||||
|
|
|
@ -47,13 +47,14 @@ class Sync:
|
||||||
:return: A tuple of:
|
:return: A tuple of:
|
||||||
|
|
||||||
1. The device from the database (with an ID) whose
|
1. The device from the database (with an ID) whose
|
||||||
``components`` field contain the db version
|
``components`` field contain the db algorithm_version
|
||||||
of the passed-in components.
|
of the passed-in components.
|
||||||
2. A list of Add / Remove (not yet added to session).
|
2. A list of Add / Remove (not yet added to session).
|
||||||
"""
|
"""
|
||||||
db_device = self.execute_register(device)
|
db_device = self.execute_register(device)
|
||||||
db_components, events = OrderedSet(), OrderedSet()
|
db_components, events = OrderedSet(), OrderedSet()
|
||||||
if components is not None: # We have component info (see above)
|
if components is not None: # We have component info (see above)
|
||||||
|
assert isinstance(db_device, Computer)
|
||||||
blacklist = set() # type: Set[int]
|
blacklist = set() # type: Set[int]
|
||||||
not_new_components = set()
|
not_new_components = set()
|
||||||
for component in components:
|
for component in components:
|
||||||
|
@ -122,7 +123,7 @@ class Sync:
|
||||||
This method tries to get an existing device using the HID
|
This method tries to get an existing device using the HID
|
||||||
or one of the tags, and...
|
or one of the tags, and...
|
||||||
|
|
||||||
- if it already exists it returns a "local synced version"
|
- if it already exists it returns a "local synced algorithm_version"
|
||||||
–the same ``device`` you passed-in but with updated values
|
–the same ``device`` you passed-in but with updated values
|
||||||
from the database. In this case we do not
|
from the database. In this case we do not
|
||||||
"touch" any of its values on the DB.
|
"touch" any of its values on the DB.
|
||||||
|
@ -187,7 +188,7 @@ class Sync:
|
||||||
setattr(db_device, field_name, value)
|
setattr(db_device, field_name, value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_remove(device: Device,
|
def add_remove(device: Computer,
|
||||||
components: Set[Component]) -> OrderedSet:
|
components: Set[Component]) -> OrderedSet:
|
||||||
"""
|
"""
|
||||||
Generates the Add and Remove events (but doesn't add them to
|
Generates the Add and Remove events (but doesn't add them to
|
||||||
|
@ -209,7 +210,7 @@ class Sync:
|
||||||
adding = components - old_components
|
adding = components - old_components
|
||||||
if adding:
|
if adding:
|
||||||
# For the components we are adding, let's remove them from their old parents
|
# For the components we are adding, let's remove them from their old parents
|
||||||
def g_parent(component: Component) -> int:
|
def g_parent(component: Component) -> Device:
|
||||||
return component.parent or Computer(id=0) # Computer with id 0 is our Identity
|
return component.parent or Computer(id=0) # Computer with id 0 is our Identity
|
||||||
|
|
||||||
for parent, _components in groupby(sorted(adding, key=g_parent), key=g_parent):
|
for parent, _components in groupby(sorted(adding, key=g_parent), key=g_parent):
|
||||||
|
|
127
ereuse_devicehub/resources/enums.py
Normal file
127
ereuse_devicehub/resources/enums.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
from distutils.version import StrictVersion
|
||||||
|
from enum import Enum, IntEnum, unique
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class SnapshotSoftware(Enum):
|
||||||
|
"""The algorithm_software used to perform the Snapshot."""
|
||||||
|
Workbench = 'Workbench'
|
||||||
|
AndroidApp = 'AndroidApp'
|
||||||
|
Web = 'Web'
|
||||||
|
DesktopApp = 'DesktopApp'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class RatingSoftware(Enum):
|
||||||
|
"""The algorithm_software used to compute the Score."""
|
||||||
|
Ereuse = 'Ereuse'
|
||||||
|
|
||||||
|
|
||||||
|
RATE_POSITIVE = 0, 10
|
||||||
|
RATE_NEGATIVE = -3, 5
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class RatingRange(IntEnum):
|
||||||
|
"""
|
||||||
|
The human translation to score range.
|
||||||
|
|
||||||
|
You can compare them: ScoreRange.VERY_LOW < ScoreRange.LOW
|
||||||
|
"""
|
||||||
|
VERY_LOW = 2
|
||||||
|
LOW = 3
|
||||||
|
MEDIUM = 4
|
||||||
|
HIGH = 5
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_score(cls, val: Union[int, float]) -> 'RatingRange':
|
||||||
|
assert 0 <= val <= 10, 'Value is not a valid score.'
|
||||||
|
|
||||||
|
if val <= cls.VERY_LOW:
|
||||||
|
return cls.VERY_LOW
|
||||||
|
elif val <= cls.LOW:
|
||||||
|
return cls.LOW
|
||||||
|
elif val <= cls.MEDIUM:
|
||||||
|
return cls.MEDIUM
|
||||||
|
else:
|
||||||
|
return cls.HIGH
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class AggregateRatingVersions(Enum):
|
||||||
|
v1 = StrictVersion('1.0')
|
||||||
|
"""
|
||||||
|
This algorithm_version is set to aggregate :class:`ereuse_devicehub.resources.
|
||||||
|
event.models.WorkbenchRate` algorithm_version X and :class:`ereuse_devicehub.
|
||||||
|
resources.event.models.PhotoboxRate` algorithm_version Y.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class AppearanceRange(Enum):
|
||||||
|
"""Grades the imperfections that aesthetically affect the device, but not its usage."""
|
||||||
|
Z = '0. The device is new.'
|
||||||
|
A = 'A. Is like new (without visual damage)'
|
||||||
|
B = 'B. Is in really good condition (small visual damage in difficult places to spot)'
|
||||||
|
C = 'C. Is in good condition (small visual damage in parts that are easy to spot, not screens)'
|
||||||
|
D = 'D. Is acceptable (visual damage in visible parts, not screens)'
|
||||||
|
E = 'E. Is unacceptable (considerable visual damage that can affect usage)'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class FunctionalityRange(Enum):
|
||||||
|
"""Grades the defects of a device that affect its usage."""
|
||||||
|
# todo sync with https://github.com/ereuse/rdevicescore#input
|
||||||
|
A = 'A. Everything works perfectly (buttons, and in case of screens there are no scratches)'
|
||||||
|
B = 'B. There is a button difficult to press or a small scratch in an edge of a screen'
|
||||||
|
C = 'C. A non-important button (or similar) doesn\'t work; screen has multiple scratches in edges'
|
||||||
|
D = 'D. Multiple buttons don\'t work; screen has visual damage resulting in uncomfortable usage'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class Bios(Enum):
|
||||||
|
"""How difficult it has been to set the bios to boot from the network."""
|
||||||
|
A = 'A. If by pressing a key you could access a boot menu with the network boot'
|
||||||
|
B = 'B. You had to get into the BIOS, and in less than 5 steps you could set the network boot'
|
||||||
|
C = 'C. Like B, but with more than 5 steps'
|
||||||
|
D = 'D. Like B or C, but you had to unlock the BIOS (i.e. by removing the battery)'
|
||||||
|
E = 'E. The device could not be booted through the network.'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class Orientation(Enum):
|
||||||
|
Vertical = 'vertical'
|
||||||
|
Horizontal = 'Horizontal'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class TestHardDriveLength(Enum):
|
||||||
|
Short = 'Short'
|
||||||
|
Extended = 'Extended'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class ImageSoftware(Enum):
|
||||||
|
Photobox = 'Photobox'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class ImageMimeTypes(Enum):
|
||||||
|
"""Supported image Mimetypes for Devicehub."""
|
||||||
|
jpg = 'image/jpeg'
|
||||||
|
png = 'image/png'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class SnapshotExpectedEvents(Enum):
|
||||||
|
"""Events that Workbench can perform when processing a device."""
|
||||||
|
TestDataStorage = 'TestDataStorage'
|
||||||
|
StressTest = 'StressTest'
|
||||||
|
EraseSectors = 'EraseSectors'
|
||||||
|
Install = 'Install'
|
||||||
|
|
||||||
|
|
||||||
|
BOX_RATE_5 = 1, 5
|
||||||
|
BOX_RATE_3 = 1, 3
|
|
@ -1,8 +1,9 @@
|
||||||
from typing import Callable, Iterable, Tuple
|
from typing import Callable, Iterable, Tuple
|
||||||
|
|
||||||
from ereuse_devicehub.resources.device.sync import Sync
|
from ereuse_devicehub.resources.device.sync import Sync
|
||||||
from ereuse_devicehub.resources.event.schemas import Add, Event, Remove, Snapshot, Test, \
|
from ereuse_devicehub.resources.event.schemas import Add, AggregateRate, EraseBasic, Event, \
|
||||||
TestHardDrive
|
Install, PhotoboxSystemRate, PhotoboxUserRate, Rate, Remove, Snapshot, Step, StepRandom, \
|
||||||
|
StepZero, Test, TestDataStorage, WorkbenchRate, EraseSectors
|
||||||
from ereuse_devicehub.resources.event.views import EventView, SnapshotView
|
from ereuse_devicehub.resources.event.views import EventView, SnapshotView
|
||||||
from teal.resource import Converters, Resource
|
from teal.resource import Converters, Resource
|
||||||
|
|
||||||
|
@ -22,6 +23,50 @@ class RemoveDef(EventDef):
|
||||||
SCHEMA = Remove
|
SCHEMA = Remove
|
||||||
|
|
||||||
|
|
||||||
|
class EraseBasicDef(EventDef):
|
||||||
|
SCHEMA = EraseBasic
|
||||||
|
|
||||||
|
|
||||||
|
class EraseSectorsDef(EraseBasicDef):
|
||||||
|
SCHEMA = EraseSectors
|
||||||
|
|
||||||
|
|
||||||
|
class StepDef(Resource):
|
||||||
|
SCHEMA = Step
|
||||||
|
|
||||||
|
|
||||||
|
class StepZeroDef(StepDef):
|
||||||
|
SCHEMA = StepZero
|
||||||
|
|
||||||
|
|
||||||
|
class StepRandomDef(StepDef):
|
||||||
|
SCHEMA = StepRandom
|
||||||
|
|
||||||
|
|
||||||
|
class RateDef(EventDef):
|
||||||
|
SCHEMA = Rate
|
||||||
|
|
||||||
|
|
||||||
|
class AggregateRateDef(RateDef):
|
||||||
|
SCHEMA = AggregateRate
|
||||||
|
|
||||||
|
|
||||||
|
class WorkbenchRateDef(RateDef):
|
||||||
|
SCHEMA = WorkbenchRate
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxUserDef(RateDef):
|
||||||
|
SCHEMA = PhotoboxUserRate
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxSystemRateDef(RateDef):
|
||||||
|
SCHEMA = PhotoboxSystemRate
|
||||||
|
|
||||||
|
|
||||||
|
class InstallDef(EventDef):
|
||||||
|
SCHEMA = Install
|
||||||
|
|
||||||
|
|
||||||
class SnapshotDef(EventDef):
|
class SnapshotDef(EventDef):
|
||||||
SCHEMA = Snapshot
|
SCHEMA = Snapshot
|
||||||
VIEW = SnapshotView
|
VIEW = SnapshotView
|
||||||
|
@ -38,5 +83,5 @@ class TestDef(EventDef):
|
||||||
SCHEMA = Test
|
SCHEMA = Test
|
||||||
|
|
||||||
|
|
||||||
class TestHardDriveDef(TestDef):
|
class TestDataStorageDef(TestDef):
|
||||||
SCHEMA = TestHardDrive
|
SCHEMA = TestDataStorage
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class StepTypes(Enum):
|
|
||||||
Zeros = 1
|
|
||||||
Random = 2
|
|
||||||
|
|
||||||
|
|
||||||
class SoftwareType(Enum):
|
|
||||||
"""The software used to perform the Snapshot."""
|
|
||||||
Workbench = 'Workbench'
|
|
||||||
AndroidApp = 'AndroidApp'
|
|
||||||
Web = 'Web'
|
|
||||||
DesktopApp = 'DesktopApp'
|
|
||||||
|
|
||||||
|
|
||||||
class Appearance(Enum):
|
|
||||||
"""Grades the imperfections that aesthetically affect the device, but not its usage."""
|
|
||||||
Z = '0. The device is new.'
|
|
||||||
A = 'A. Is like new (without visual damage)'
|
|
||||||
B = 'B. Is in really good condition (small visual damage in difficult places to spot)'
|
|
||||||
C = 'C. Is in good condition (small visual damage in parts that are easy to spot, not screens)'
|
|
||||||
D = 'D. Is acceptable (visual damage in visible parts, not screens)'
|
|
||||||
E = 'E. Is unacceptable (considerable visual damage that can affect usage)'
|
|
||||||
|
|
||||||
|
|
||||||
class Functionality(Enum):
|
|
||||||
"""Grades the defects of a device that affect its usage."""
|
|
||||||
A = 'A. Everything works perfectly (buttons, and in case of screens there are no scratches)'
|
|
||||||
B = 'B. There is a button difficult to press or a small scratch in an edge of a screen'
|
|
||||||
C = 'C. A non-important button (or similar) doesn\'t work; screen has multiple scratches in edges'
|
|
||||||
D = 'D. Multiple buttons don\'t work; screen has visual damage resulting in uncomfortable usage'
|
|
||||||
|
|
||||||
|
|
||||||
class Bios(Enum):
|
|
||||||
"""How difficult it has been to set the bios to boot from the network."""
|
|
||||||
A = 'A. If by pressing a key you could access a boot menu with the network boot'
|
|
||||||
B = 'B. You had to get into the BIOS, and in less than 5 steps you could set the network boot'
|
|
||||||
C = 'C. Like B, but with more than 5 steps'
|
|
||||||
D = 'D. Like B or C, but you had to unlock the BIOS (i.e. by removing the battery)'
|
|
||||||
E = 'E. The device could not be booted through the network.'
|
|
||||||
|
|
||||||
|
|
||||||
class Orientation(Enum):
|
|
||||||
Vertical = 'vertical'
|
|
||||||
Horizontal = 'Horizontal'
|
|
||||||
|
|
||||||
|
|
||||||
class TestHardDriveLength(Enum):
|
|
||||||
Short = 'Short'
|
|
||||||
Extended = 'Extended'
|
|
|
@ -1,48 +1,55 @@
|
||||||
from datetime import timedelta
|
from uuid import uuid4
|
||||||
|
|
||||||
from colour import Color
|
|
||||||
from flask import g
|
from flask import g
|
||||||
from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, DateTime, Enum as DBEnum, \
|
from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, DateTime, Enum as DBEnum, \
|
||||||
ForeignKey, Integer, Interval, JSON, Sequence, SmallInteger, Unicode
|
Float, ForeignKey, Interval, JSON, SmallInteger, Unicode, event
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from sqlalchemy.orm import backref, relationship, validates
|
from sqlalchemy.ext.orderinglist import ordering_list
|
||||||
|
from sqlalchemy.orm import backref, relationship
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
from sqlalchemy_utils import ColorType
|
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.device.models import Component, Device
|
from ereuse_devicehub.resources.device.models import Component, DataStorage, Device
|
||||||
from ereuse_devicehub.resources.event.enums import Appearance, Bios, Functionality, Orientation, \
|
from ereuse_devicehub.resources.enums import AppearanceRange, BOX_RATE_3, BOX_RATE_5, Bios, \
|
||||||
SoftwareType, StepTypes, TestHardDriveLength
|
FunctionalityRange, RATE_NEGATIVE, RATE_POSITIVE, RatingRange, RatingSoftware, \
|
||||||
|
SnapshotExpectedEvents, SnapshotSoftware, TestHardDriveLength
|
||||||
|
from ereuse_devicehub.resources.image.models import Image
|
||||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE, STR_SM_SIZE, Thing
|
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE, STR_SM_SIZE, Thing
|
||||||
from ereuse_devicehub.resources.user.models import User
|
from ereuse_devicehub.resources.user.models import User
|
||||||
from teal.db import CASCADE, CASCADE_OWN, INHERIT_COND, POLYMORPHIC_ID, POLYMORPHIC_ON, \
|
from teal.db import ArrayOfEnum, CASCADE, CASCADE_OWN, INHERIT_COND, POLYMORPHIC_ID, \
|
||||||
StrictVersionType, check_range
|
POLYMORPHIC_ON, StrictVersionType, check_range
|
||||||
|
|
||||||
|
|
||||||
class JoinedTableMixin:
|
class JoinedTableMixin:
|
||||||
|
# noinspection PyMethodParameters
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def id(cls):
|
def id(cls):
|
||||||
return Column(BigInteger, ForeignKey(Event.id), primary_key=True)
|
return Column(UUID(as_uuid=True), ForeignKey(Event.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
class Event(Thing):
|
class Event(Thing):
|
||||||
id = Column(BigInteger, Sequence('event_seq'), primary_key=True)
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||||
title = Column(Unicode(STR_BIG_SIZE), default='', nullable=False)
|
title = Column(Unicode(STR_BIG_SIZE), default='', nullable=False)
|
||||||
date = Column(DateTime)
|
|
||||||
secured = Column(Boolean, default=False, nullable=False)
|
|
||||||
type = Column(Unicode)
|
type = Column(Unicode)
|
||||||
incidence = Column(Boolean, default=False, nullable=False)
|
incidence = Column(Boolean, default=False, nullable=False)
|
||||||
|
closed = Column(Boolean, default=True, nullable=False)
|
||||||
|
"""
|
||||||
|
Whether the author has finished the event.
|
||||||
|
After this is set to True, no modifications are allowed.
|
||||||
|
"""
|
||||||
|
error = Column(Boolean, default=False, nullable=False)
|
||||||
description = Column(Unicode, default='', nullable=False)
|
description = Column(Unicode, default='', nullable=False)
|
||||||
|
date = Column(DateTime)
|
||||||
|
|
||||||
snapshot_id = Column(BigInteger, ForeignKey('snapshot.id',
|
snapshot_id = Column(UUID(as_uuid=True), ForeignKey('snapshot.id',
|
||||||
use_alter=True,
|
use_alter=True,
|
||||||
name='snapshot_events'))
|
name='snapshot_events'))
|
||||||
snapshot = relationship('Snapshot',
|
snapshot = relationship('Snapshot',
|
||||||
backref=backref('events',
|
backref=backref('events',
|
||||||
lazy=True,
|
lazy=True,
|
||||||
cascade=CASCADE,
|
cascade=CASCADE_OWN,
|
||||||
collection_class=OrderedSet),
|
collection_class=set),
|
||||||
primaryjoin='Event.snapshot_id == Snapshot.id')
|
primaryjoin='Event.snapshot_id == Snapshot.id')
|
||||||
|
|
||||||
author_id = Column(UUID(as_uuid=True),
|
author_id = Column(UUID(as_uuid=True),
|
||||||
|
@ -55,12 +62,23 @@ class Event(Thing):
|
||||||
components = relationship(Component,
|
components = relationship(Component,
|
||||||
backref=backref('events_components',
|
backref=backref('events_components',
|
||||||
lazy=True,
|
lazy=True,
|
||||||
order_by=lambda: Event.id,
|
order_by=lambda: Event.created,
|
||||||
collection_class=OrderedSet),
|
collection_class=OrderedSet),
|
||||||
secondary=lambda: EventComponent.__table__,
|
secondary=lambda: EventComponent.__table__,
|
||||||
order_by=lambda: Device.id,
|
order_by=lambda: Component.id,
|
||||||
collection_class=OrderedSet)
|
collection_class=OrderedSet)
|
||||||
|
"""
|
||||||
|
The components that are affected by the event.
|
||||||
|
|
||||||
|
When performing events to parent devices their components are
|
||||||
|
affected too.
|
||||||
|
|
||||||
|
For example: an ``Allocate`` is performed to a Computer and this
|
||||||
|
relationship is filled with the components the computer had
|
||||||
|
at the time of the event.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# noinspection PyMethodParameters
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def __mapper_args__(cls):
|
def __mapper_args__(cls):
|
||||||
"""
|
"""
|
||||||
|
@ -79,8 +97,8 @@ class Event(Thing):
|
||||||
|
|
||||||
|
|
||||||
class EventComponent(db.Model):
|
class EventComponent(db.Model):
|
||||||
device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
device_id = Column(BigInteger, ForeignKey(Component.id), primary_key=True)
|
||||||
event_id = Column(BigInteger, ForeignKey(Event.id), primary_key=True)
|
event_id = Column(UUID(as_uuid=True), ForeignKey(Event.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
class EventWithOneDevice(Event):
|
class EventWithOneDevice(Event):
|
||||||
|
@ -89,22 +107,19 @@ class EventWithOneDevice(Event):
|
||||||
backref=backref('events_one',
|
backref=backref('events_one',
|
||||||
lazy=True,
|
lazy=True,
|
||||||
cascade=CASCADE,
|
cascade=CASCADE,
|
||||||
order_by=lambda: EventWithOneDevice.id,
|
order_by=lambda: EventWithOneDevice.created,
|
||||||
collection_class=OrderedSet),
|
collection_class=OrderedSet),
|
||||||
primaryjoin=Device.id == device_id)
|
primaryjoin=Device.id == device_id)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return '<{0.t} {0.id!r} device={0.device!r}>'.format(self)
|
return '<{0.t} {0.id!r} device={0.device_id}>'.format(self)
|
||||||
|
|
||||||
|
|
||||||
class EventWithMultipleDevices(Event):
|
class EventWithMultipleDevices(Event):
|
||||||
"""
|
|
||||||
Note that these events are not deleted when a device is deleted.
|
|
||||||
"""
|
|
||||||
devices = relationship(Device,
|
devices = relationship(Device,
|
||||||
backref=backref('events_multiple',
|
backref=backref('events_multiple',
|
||||||
lazy=True,
|
lazy=True,
|
||||||
order_by=lambda: EventWithMultipleDevices.id,
|
order_by=lambda: EventWithMultipleDevices.created,
|
||||||
collection_class=OrderedSet),
|
collection_class=OrderedSet),
|
||||||
secondary=lambda: EventDevice.__table__,
|
secondary=lambda: EventDevice.__table__,
|
||||||
order_by=lambda: Device.id)
|
order_by=lambda: Device.id)
|
||||||
|
@ -115,7 +130,8 @@ class EventWithMultipleDevices(Event):
|
||||||
|
|
||||||
class EventDevice(db.Model):
|
class EventDevice(db.Model):
|
||||||
device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
event_id = Column(BigInteger, ForeignKey(EventWithMultipleDevices.id), primary_key=True)
|
event_id = Column(UUID(as_uuid=True), ForeignKey(EventWithMultipleDevices.id),
|
||||||
|
primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
class Add(EventWithOneDevice):
|
class Add(EventWithOneDevice):
|
||||||
|
@ -139,11 +155,11 @@ class Deallocate(JoinedTableMixin, EventWithMultipleDevices):
|
||||||
|
|
||||||
|
|
||||||
class EraseBasic(JoinedTableMixin, EventWithOneDevice):
|
class EraseBasic(JoinedTableMixin, EventWithOneDevice):
|
||||||
starting_time = Column(DateTime, nullable=False)
|
start_time = Column(DateTime, nullable=False)
|
||||||
ending_time = Column(DateTime, nullable=False)
|
end_time = Column(DateTime, CheckConstraint('end_time > start_time'), nullable=False)
|
||||||
secure_random_steps = Column(SmallInteger, check_range('secure_random_steps', min=0),
|
secure_random_steps = Column(SmallInteger,
|
||||||
|
check_range('secure_random_steps', min=0),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
success = Column(Boolean, nullable=False)
|
|
||||||
clean_with_zeros = Column(Boolean, nullable=False)
|
clean_with_zeros = Column(Boolean, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
@ -152,54 +168,63 @@ class EraseSectors(EraseBasic):
|
||||||
|
|
||||||
|
|
||||||
class Step(db.Model):
|
class Step(db.Model):
|
||||||
id = Column(BigInteger, Sequence('step_seq'), primary_key=True)
|
erasure_id = Column(UUID(as_uuid=True), ForeignKey(EraseBasic.id), primary_key=True)
|
||||||
num = Column(SmallInteger, nullable=False)
|
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
||||||
type = Column(DBEnum(StepTypes), nullable=False)
|
num = Column(SmallInteger, primary_key=True)
|
||||||
success = Column(Boolean, nullable=False)
|
error = Column(Boolean, default=False, nullable=False)
|
||||||
starting_time = Column(DateTime, nullable=False)
|
start_time = Column(DateTime, nullable=False)
|
||||||
ending_time = Column(DateTime, CheckConstraint('ending_time > starting_time'), nullable=False)
|
end_time = Column(DateTime, CheckConstraint('end_time > start_time'), nullable=False)
|
||||||
secure_random_steps = Column(SmallInteger, check_range('secure_random_steps', min=0),
|
secure_random_steps = Column(SmallInteger,
|
||||||
|
check_range('secure_random_steps', min=0),
|
||||||
nullable=False)
|
nullable=False)
|
||||||
clean_with_zeros = Column(Boolean, nullable=False)
|
clean_with_zeros = Column(Boolean, nullable=False)
|
||||||
|
|
||||||
erasure_id = Column(BigInteger, ForeignKey(EraseBasic.id))
|
erasure = relationship(EraseBasic,
|
||||||
erasure = relationship(EraseBasic, backref=backref('steps', cascade=CASCADE_OWN))
|
backref=backref('steps',
|
||||||
|
cascade=CASCADE_OWN,
|
||||||
|
order_by=num,
|
||||||
|
collection_class=ordering_list('num')))
|
||||||
|
|
||||||
|
# noinspection PyMethodParameters
|
||||||
|
@declared_attr
|
||||||
|
def __mapper_args__(cls):
|
||||||
|
"""
|
||||||
|
Defines inheritance.
|
||||||
|
|
||||||
|
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
||||||
|
extensions/declarative/api.html
|
||||||
|
#sqlalchemy.ext.declarative.declared_attr>`_
|
||||||
|
"""
|
||||||
|
args = {POLYMORPHIC_ID: cls.t}
|
||||||
|
if cls.t == 'Step':
|
||||||
|
args[POLYMORPHIC_ON] = cls.type
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
class StepZero(Step):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StepRandom(Step):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Snapshot(JoinedTableMixin, EventWithOneDevice):
|
class Snapshot(JoinedTableMixin, EventWithOneDevice):
|
||||||
uuid = Column(UUID(as_uuid=True), nullable=False, unique=True) # type: UUID
|
uuid = Column(UUID(as_uuid=True), nullable=False, unique=True)
|
||||||
version = Column(StrictVersionType(STR_SM_SIZE), nullable=False) # type: str
|
version = Column(StrictVersionType(STR_SM_SIZE), nullable=False)
|
||||||
software = Column(DBEnum(SoftwareType), nullable=False) # type: SoftwareType
|
software = Column(DBEnum(SnapshotSoftware), nullable=False)
|
||||||
appearance = Column(DBEnum(Appearance)) # type: Appearance
|
elapsed = Column(Interval, nullable=False)
|
||||||
appearance_score = Column(SmallInteger,
|
expected_events = Column(ArrayOfEnum(DBEnum(SnapshotExpectedEvents)))
|
||||||
check_range('appearance_score', -3, 5)) # type: int
|
|
||||||
functionality = Column(DBEnum(Functionality)) # type: Functionality
|
|
||||||
functionality_score = Column(SmallInteger,
|
|
||||||
check_range('functionality_score', min=-3, max=5)) # type: int
|
|
||||||
labelling = Column(Boolean) # type: bool
|
|
||||||
bios = Column(DBEnum(Bios)) # type: Bios
|
|
||||||
condition = Column(SmallInteger,
|
|
||||||
check_range('condition', min=0, max=5)) # type: int
|
|
||||||
elapsed = Column(Interval, nullable=False) # type: timedelta
|
|
||||||
install_name = Column(Unicode(STR_BIG_SIZE)) # type: str
|
|
||||||
install_elapsed = Column(Interval) # type: timedelta
|
|
||||||
install_success = Column(Boolean) # type: bool
|
|
||||||
inventory_elapsed = Column(Interval) # type: timedelta
|
|
||||||
color = Column(ColorType) # type: Color
|
|
||||||
orientation = Column(DBEnum(Orientation)) # type: Orientation
|
|
||||||
|
|
||||||
@validates('components')
|
|
||||||
def validate_components_only_workbench(self, _, components):
|
class Install(JoinedTableMixin, EventWithOneDevice):
|
||||||
if self.software != SoftwareType.Workbench:
|
name = Column(Unicode(STR_BIG_SIZE), nullable=False)
|
||||||
if components:
|
elapsed = Column(Interval, nullable=False)
|
||||||
raise ValueError('Only Snapshots from Workbench can have components.')
|
|
||||||
return components
|
|
||||||
|
|
||||||
|
|
||||||
class SnapshotRequest(db.Model):
|
class SnapshotRequest(db.Model):
|
||||||
id = Column(BigInteger, ForeignKey(Snapshot.id), primary_key=True)
|
id = Column(UUID(as_uuid=True), ForeignKey(Snapshot.id), primary_key=True)
|
||||||
request = Column(JSON, nullable=False)
|
request = Column(JSON, nullable=False)
|
||||||
|
|
||||||
snapshot = relationship(Snapshot,
|
snapshot = relationship(Snapshot,
|
||||||
backref=backref('request',
|
backref=backref('request',
|
||||||
lazy=True,
|
lazy=True,
|
||||||
|
@ -207,25 +232,132 @@ class SnapshotRequest(db.Model):
|
||||||
cascade=CASCADE_OWN))
|
cascade=CASCADE_OWN))
|
||||||
|
|
||||||
|
|
||||||
|
class Rate(JoinedTableMixin, EventWithOneDevice):
|
||||||
|
rating = Column(Float(decimal_return_scale=2), check_range('rating', *RATE_POSITIVE))
|
||||||
|
algorithm_software = Column(DBEnum(RatingSoftware), nullable=False)
|
||||||
|
algorithm_version = Column(StrictVersionType, nullable=False)
|
||||||
|
appearance = Column(Float(decimal_return_scale=2), check_range('appearance', *RATE_NEGATIVE))
|
||||||
|
functionality = Column(Float(decimal_return_scale=2),
|
||||||
|
check_range('functionality', *RATE_NEGATIVE))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rating_range(self) -> RatingRange:
|
||||||
|
return RatingRange.from_score(self.rating)
|
||||||
|
|
||||||
|
|
||||||
|
class IndividualRate(Rate):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AggregateRate(Rate):
|
||||||
|
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||||
|
ratings = relationship(IndividualRate,
|
||||||
|
backref=backref('aggregated_ratings',
|
||||||
|
lazy=True,
|
||||||
|
order_by=lambda: IndividualRate.created,
|
||||||
|
collection_class=OrderedSet),
|
||||||
|
secondary=lambda: RateAggregateRate.__table__,
|
||||||
|
order_by=lambda: IndividualRate.created,
|
||||||
|
collection_class=OrderedSet)
|
||||||
|
"""The ratings this aggregateRate aggregates."""
|
||||||
|
|
||||||
|
|
||||||
|
class RateAggregateRate(db.Model):
|
||||||
|
"""
|
||||||
|
Represents the ``many to many`` relationship between
|
||||||
|
``Rate`` and ``AggregateRate``.
|
||||||
|
"""
|
||||||
|
rate_id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||||
|
aggregate_rate_id = Column(UUID(as_uuid=True),
|
||||||
|
ForeignKey(AggregateRate.id),
|
||||||
|
primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkbenchRate(IndividualRate):
|
||||||
|
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||||
|
processor = Column(Float(decimal_return_scale=2), check_range('processor', *RATE_POSITIVE))
|
||||||
|
ram = Column(Float(decimal_return_scale=2), check_range('ram', *RATE_POSITIVE))
|
||||||
|
data_storage = Column(Float(decimal_return_scale=2),
|
||||||
|
check_range('data_storage', *RATE_POSITIVE))
|
||||||
|
graphic_card = Column(Float(decimal_return_scale=2),
|
||||||
|
check_range('graphic_card', *RATE_POSITIVE))
|
||||||
|
labelling = Column(Boolean)
|
||||||
|
bios = Column(DBEnum(Bios))
|
||||||
|
appearance_range = Column(DBEnum(AppearanceRange))
|
||||||
|
functionality_range = Column(DBEnum(FunctionalityRange))
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxRate(IndividualRate):
|
||||||
|
id = Column(UUID(as_uuid=True), ForeignKey(Rate.id), primary_key=True)
|
||||||
|
image_id = Column(UUID(as_uuid=True), ForeignKey(Image.id), nullable=False)
|
||||||
|
image = relationship(Image,
|
||||||
|
uselist=False,
|
||||||
|
cascade=CASCADE_OWN,
|
||||||
|
single_parent=True,
|
||||||
|
primaryjoin=Image.id == image_id)
|
||||||
|
|
||||||
|
# todo how to ensure phtoboxrate.device == image.image_list.device?
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxUserRate(PhotoboxRate):
|
||||||
|
id = Column(UUID(as_uuid=True), ForeignKey(PhotoboxRate.id), primary_key=True)
|
||||||
|
assembling = Column(SmallInteger, check_range('assembling', *BOX_RATE_5), nullable=False)
|
||||||
|
parts = Column(SmallInteger, check_range('parts', *BOX_RATE_5), nullable=False)
|
||||||
|
buttons = Column(SmallInteger, check_range('buttons', *BOX_RATE_5), nullable=False)
|
||||||
|
dents = Column(SmallInteger, check_range('dents', *BOX_RATE_5), nullable=False)
|
||||||
|
decolorization = Column(SmallInteger,
|
||||||
|
check_range('decolorization', *BOX_RATE_5),
|
||||||
|
nullable=False)
|
||||||
|
scratches = Column(SmallInteger, check_range('scratches', *BOX_RATE_5), nullable=False)
|
||||||
|
tag_alignment = Column(SmallInteger,
|
||||||
|
check_range('tag_alignment', *BOX_RATE_3),
|
||||||
|
nullable=False)
|
||||||
|
tag_adhesive = Column(SmallInteger, check_range('tag_adhesive', *BOX_RATE_3), nullable=False)
|
||||||
|
dirt = Column(SmallInteger, check_range('dirt', *BOX_RATE_3), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxSystemRate(PhotoboxRate):
|
||||||
|
id = Column(UUID(as_uuid=True), ForeignKey(PhotoboxRate.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
class Test(JoinedTableMixin, EventWithOneDevice):
|
class Test(JoinedTableMixin, EventWithOneDevice):
|
||||||
elapsed = Column(Interval, nullable=False)
|
elapsed = Column(Interval, nullable=False)
|
||||||
success = Column(Boolean, nullable=False)
|
|
||||||
|
|
||||||
snapshot = relationship(Snapshot, backref=backref('tests',
|
|
||||||
lazy=True,
|
|
||||||
cascade=CASCADE_OWN,
|
|
||||||
order_by=Event.id,
|
|
||||||
collection_class=OrderedSet))
|
|
||||||
|
|
||||||
|
|
||||||
class TestHardDrive(Test):
|
class TestDataStorage(Test):
|
||||||
id = Column(BigInteger, ForeignKey(Test.id), primary_key=True)
|
id = Column(UUID(as_uuid=True), ForeignKey(Test.id), primary_key=True)
|
||||||
length = Column(DBEnum(TestHardDriveLength), nullable=False) # todo from type
|
length = Column(DBEnum(TestHardDriveLength), nullable=False) # todo from type
|
||||||
status = Column(Unicode(STR_SIZE), nullable=False)
|
status = Column(Unicode(STR_SIZE), nullable=False)
|
||||||
lifetime = Column(Interval, nullable=False)
|
lifetime = Column(Interval, nullable=False)
|
||||||
first_error = Column(Integer)
|
first_error = Column(SmallInteger, nullable=False, default=0)
|
||||||
# todo error becomes Test.success
|
passed_lifetime = Column(Interval)
|
||||||
|
assessment = Column(Boolean)
|
||||||
|
reallocated_sector_count = Column(SmallInteger)
|
||||||
|
power_cycle_count = Column(SmallInteger)
|
||||||
|
reported_uncorrectable_errors = Column(SmallInteger)
|
||||||
|
command_timeout = Column(SmallInteger)
|
||||||
|
current_pending_sector_count = Column(SmallInteger)
|
||||||
|
offline_uncorrectable = Column(SmallInteger)
|
||||||
|
remaining_lifetime_percentage = Column(SmallInteger)
|
||||||
|
|
||||||
|
# todo remove lifetime / passed_lifetime as I think they are the same
|
||||||
|
|
||||||
|
|
||||||
class StressTest(Test):
|
class StressTest(Test):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Listeners
|
||||||
|
@event.listens_for(TestDataStorage.device, 'set', retval=True, propagate=True)
|
||||||
|
@event.listens_for(Install.device, 'set', retval=True, propagate=True)
|
||||||
|
@event.listens_for(EraseBasic.device, 'set', retval=True, propagate=True)
|
||||||
|
def validate_device_is_data_storage(target, value, old_value, initiator):
|
||||||
|
if not isinstance(value, DataStorage):
|
||||||
|
raise TypeError('{} must be a DataStorage but you passed {}'.format(initiator.impl, value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
# todo finish adding events
|
||||||
|
# @event.listens_for(Install.snapshot, 'before_insert', propagate=True)
|
||||||
|
# def validate_required_snapshot(mapper, connection, target: Event):
|
||||||
|
# if not target.snapshot:
|
||||||
|
# raise ValidationError('{0!r} must be linked to a Snapshot.'.format(target))
|
||||||
|
|
214
ereuse_devicehub/resources/event/models.pyi
Normal file
214
ereuse_devicehub/resources/event/models.pyi
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from distutils.version import StrictVersion
|
||||||
|
from typing import List, Set
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.device.models import Component, Computer, Device
|
||||||
|
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
|
||||||
|
RatingSoftware, SnapshotSoftware, TestHardDriveLength, SnapshotExpectedEvents
|
||||||
|
from ereuse_devicehub.resources.image.models import Image
|
||||||
|
from ereuse_devicehub.resources.models import Thing
|
||||||
|
from ereuse_devicehub.resources.user import User
|
||||||
|
from teal.db import Model
|
||||||
|
|
||||||
|
|
||||||
|
class Event(Thing):
|
||||||
|
id = ... # type: Column
|
||||||
|
title = ... # type: Column
|
||||||
|
date = ... # type: Column
|
||||||
|
type = ... # type: Column
|
||||||
|
incidence = ... # type: Column
|
||||||
|
description = ... # type: Column
|
||||||
|
finalized = ... # type: Column
|
||||||
|
snapshot_id = ... # type: Column
|
||||||
|
snapshot = ... # type: relationship
|
||||||
|
author_id = ... # type: Column
|
||||||
|
author = ... # type: relationship
|
||||||
|
components = ... # type: relationship
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.id = ... # type: UUID
|
||||||
|
self.title = ... # type: str
|
||||||
|
self.type = ... # type: str
|
||||||
|
self.incidence = ... # type: bool
|
||||||
|
self.closed = ... # type: bool
|
||||||
|
self.error = ... # type: bool
|
||||||
|
self.description = ... # type: str
|
||||||
|
self.date = ... # type: datetime
|
||||||
|
self.snapshot_id = ... # type: UUID
|
||||||
|
self.snapshot = ... # type: Snapshot
|
||||||
|
self.author_id = ... # type: UUID
|
||||||
|
self.author = ... # type: User
|
||||||
|
self.components = ... # type: Set[Component]
|
||||||
|
|
||||||
|
|
||||||
|
class EventWithOneDevice(Event):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.device_id = ... # type: int
|
||||||
|
self.device = ... # type: Device
|
||||||
|
|
||||||
|
|
||||||
|
class EventWithMultipleDevices(Event):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.devices = ... # type: Set[Device]
|
||||||
|
|
||||||
|
|
||||||
|
class Step(Model):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
self.erasure_id = ... # type: UUID
|
||||||
|
self.type = ... # type: str
|
||||||
|
self.num = ... # type: int
|
||||||
|
self.success = ... # type: bool
|
||||||
|
self.start_time = ... # type: datetime
|
||||||
|
self.end_time = ... # type: datetime
|
||||||
|
self.secure_random_steps = ... # type: int
|
||||||
|
self.clean_with_zeros = ... # type: bool
|
||||||
|
self.erasure = ... # type: EraseBasic
|
||||||
|
|
||||||
|
|
||||||
|
class StepZero(Step):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class StepRandom(Step):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Snapshot(EventWithOneDevice):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.uuid = ... # type: UUID
|
||||||
|
self.version = ... # type: StrictVersion
|
||||||
|
self.software = ... # type: SnapshotSoftware
|
||||||
|
self.elapsed = ... # type: timedelta
|
||||||
|
self.device = ... # type: Computer
|
||||||
|
self.events = ... # type: Set[Event]
|
||||||
|
self.expected_events = ... # type: List[SnapshotExpectedEvents]
|
||||||
|
|
||||||
|
|
||||||
|
class Install(EventWithOneDevice):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.name = ... # type: str
|
||||||
|
self.elapsed = ... # type: timedelta
|
||||||
|
self.success = ... # type: bool
|
||||||
|
|
||||||
|
|
||||||
|
class SnapshotRequest(Model):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
self.id = ... # type: UUID
|
||||||
|
self.request = ... # type: dict
|
||||||
|
self.snapshot = ... # type: Snapshot
|
||||||
|
|
||||||
|
|
||||||
|
class Rate(EventWithOneDevice):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.rating = ... # type: float
|
||||||
|
self.algorithm_software = ... # type: RatingSoftware
|
||||||
|
self.algorithm_version = ... # type: StrictVersion
|
||||||
|
self.appearance = ... # type: float
|
||||||
|
self.functionality = ... # type: float
|
||||||
|
self.rating_range = ... # type: str
|
||||||
|
|
||||||
|
|
||||||
|
class IndividualRate(Rate):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.aggregated_ratings = ... # type: Set[AggregateRate]
|
||||||
|
|
||||||
|
|
||||||
|
class AggregateRate(Rate):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.ratings = ... # type: Set[IndividualRate]
|
||||||
|
|
||||||
|
|
||||||
|
class WorkbenchRate(IndividualRate):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.processor = ... # type: float
|
||||||
|
self.ram = ... # type: float
|
||||||
|
self.data_storage = ... # type: float
|
||||||
|
self.graphic_card = ... # type: float
|
||||||
|
self.labelling = ... # type: bool
|
||||||
|
self.bios = ... # type: Bios
|
||||||
|
self.appearance_range = ... # type: AppearanceRange
|
||||||
|
self.functionality_range = ... # type: FunctionalityRange
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxRate(IndividualRate):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.num = ... # type: int
|
||||||
|
self.image_id = ... # type: UUID
|
||||||
|
self.image = ... # type: Image
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxUserRate(PhotoboxRate):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.assembling = ... # type: int
|
||||||
|
self.parts = ... # type: int
|
||||||
|
self.buttons = ... # type: int
|
||||||
|
self.dents = ... # type: int
|
||||||
|
self.decolorization = ... # type: int
|
||||||
|
self.scratches = ... # type: int
|
||||||
|
self.tag_adhesive = ... # type: int
|
||||||
|
self.dirt = ... # type: int
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxSystemRate(PhotoboxRate):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Test(EventWithOneDevice):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.elapsed = ... # type: timedelta
|
||||||
|
self.success = ... # type: bool
|
||||||
|
|
||||||
|
|
||||||
|
class TestDataStorage(Test):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.id = ... # type: UUID
|
||||||
|
self.length = ... # type: TestHardDriveLength
|
||||||
|
self.status = ... # type: str
|
||||||
|
self.lifetime = ... # type: timedelta
|
||||||
|
self.first_error = ... # type: int
|
||||||
|
self.passed_lifetime = ... # type: timedelta
|
||||||
|
self.assessment = ... # type: int
|
||||||
|
self.reallocated_sector_count = ... # type: int
|
||||||
|
self.power_cycle_count = ... # type: int
|
||||||
|
self.reported_uncorrectable_errors = ... # type: int
|
||||||
|
self.command_timeout = ... # type: int
|
||||||
|
self.current_pending_sector_count = ... # type: int
|
||||||
|
self.offline_uncorrectable = ... # type: int
|
||||||
|
self.remaining_lifetime_percentage = ... # type: int
|
||||||
|
|
||||||
|
|
||||||
|
class StressTest(Test):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EraseBasic(EventWithOneDevice):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.start_time = ... # type: datetime
|
||||||
|
self.end_time = ... # type: datetime
|
||||||
|
self.secure_random_steps = ... # type: int
|
||||||
|
self.steps = ... # type: List[Step]
|
||||||
|
self.clean_with_zeros = ... # type: bool
|
||||||
|
self.success = ... # type: bool
|
||||||
|
|
||||||
|
|
||||||
|
class EraseSectors(EraseBasic):
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
|
@ -1,17 +1,17 @@
|
||||||
from flask import current_app as app
|
from flask import current_app as app
|
||||||
from marshmallow import ValidationError, post_load, validates_schema
|
from marshmallow import ValidationError, validates_schema
|
||||||
from marshmallow.fields import Boolean, DateTime, Integer, Nested, String, TimeDelta, UUID
|
from marshmallow.fields import Boolean, DateTime, Float, Integer, Nested, String, TimeDelta, UUID
|
||||||
from marshmallow.validate import Length, Range
|
from marshmallow.validate import Length, Range
|
||||||
from marshmallow_enum import EnumField
|
from marshmallow_enum import EnumField
|
||||||
|
|
||||||
from ereuse_devicehub.marshmallow import NestedOn
|
from ereuse_devicehub.marshmallow import NestedOn
|
||||||
from ereuse_devicehub.resources.device.schemas import Component, Device
|
from ereuse_devicehub.resources.device.schemas import Component, Device
|
||||||
from ereuse_devicehub.resources.event.enums import Appearance, Bios, Functionality, Orientation, \
|
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
|
||||||
SoftwareType, StepTypes, TestHardDriveLength
|
RATE_POSITIVE, RatingSoftware, SnapshotExpectedEvents, SnapshotSoftware, TestHardDriveLength
|
||||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
||||||
from ereuse_devicehub.resources.schemas import Thing
|
from ereuse_devicehub.resources.schemas import Thing
|
||||||
from ereuse_devicehub.resources.user.schemas import User
|
from ereuse_devicehub.resources.user.schemas import User
|
||||||
from teal.marshmallow import Color, Version
|
from teal.marshmallow import Version
|
||||||
from teal.resource import Schema
|
from teal.resource import Schema
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,16 +23,12 @@ class Event(Thing):
|
||||||
date = DateTime('iso', description='When this event happened. '
|
date = DateTime('iso', description='When this event happened. '
|
||||||
'Leave it blank if it is happening now. '
|
'Leave it blank if it is happening now. '
|
||||||
'This is used when creating events retroactively.')
|
'This is used when creating events retroactively.')
|
||||||
secured = Boolean(default=False,
|
error = Boolean(default=False, description='Did the event fail?')
|
||||||
description='Can we ensure the info in this event is totally correct?'
|
|
||||||
'Devicehub will automatically set this too for some events,'
|
|
||||||
'for example in snapshots if it could detect the ids of the'
|
|
||||||
'hardware without margin of doubt.')
|
|
||||||
incidence = Boolean(default=False,
|
incidence = Boolean(default=False,
|
||||||
description='Was something wrong in this event?')
|
description='Should this event be reviewed due some anomaly?')
|
||||||
snapshot = NestedOn('Snapshot', dump_only=True)
|
snapshot = NestedOn('Snapshot', dump_only=True)
|
||||||
description = String(default='', description='A comment about the event.')
|
|
||||||
components = NestedOn(Component, dump_only=True, many=True)
|
components = NestedOn(Component, dump_only=True, many=True)
|
||||||
|
description = String(default='', description='A comment about the event.')
|
||||||
|
|
||||||
|
|
||||||
class EventWithOneDevice(Event):
|
class EventWithOneDevice(Event):
|
||||||
|
@ -67,12 +63,12 @@ class Deallocate(EventWithMultipleDevices):
|
||||||
|
|
||||||
|
|
||||||
class EraseBasic(EventWithOneDevice):
|
class EraseBasic(EventWithOneDevice):
|
||||||
starting_time = DateTime(required=True, data_key='startingTime')
|
start_time = DateTime(required=True, data_key='startTime')
|
||||||
ending_time = DateTime(required=True, data_key='endingTime')
|
end_time = DateTime(required=True, data_key='endTime')
|
||||||
secure_random_steps = Integer(validate=Range(min=0), required=True,
|
secure_random_steps = Integer(validate=Range(min=0), required=True,
|
||||||
data_key='secureRandomSteps')
|
data_key='secureRandomSteps')
|
||||||
success = Boolean(required=True)
|
|
||||||
clean_with_zeros = Boolean(required=True, data_key='cleanWithZeros')
|
clean_with_zeros = Boolean(required=True, data_key='cleanWithZeros')
|
||||||
|
steps = NestedOn('Step', many=True, required=True)
|
||||||
|
|
||||||
|
|
||||||
class EraseSectors(EraseBasic):
|
class EraseSectors(EraseBasic):
|
||||||
|
@ -81,46 +77,94 @@ class EraseSectors(EraseBasic):
|
||||||
|
|
||||||
class Step(Schema):
|
class Step(Schema):
|
||||||
id = Integer(dump_only=True)
|
id = Integer(dump_only=True)
|
||||||
type = EnumField(StepTypes, required=True)
|
type = String(description='Only required when it is nested.')
|
||||||
starting_time = DateTime(required=True, data_key='startingTime')
|
start_time = DateTime(required=True, data_key='startTime')
|
||||||
ending_time = DateTime(required=True, data_key='endingTime')
|
end_time = DateTime(required=True, data_key='endTime')
|
||||||
secure_random_steps = Integer(validate=Range(min=0),
|
secure_random_steps = Integer(validate=Range(min=0),
|
||||||
required=True,
|
required=True,
|
||||||
data_key='secureRandomSteps')
|
data_key='secureRandomSteps')
|
||||||
success = Boolean(required=True)
|
|
||||||
clean_with_zeros = Boolean(required=True, data_key='cleanWithZeros')
|
clean_with_zeros = Boolean(required=True, data_key='cleanWithZeros')
|
||||||
|
error = Boolean(default=False, description='Did the event fail?')
|
||||||
|
|
||||||
|
|
||||||
class Condition(Schema):
|
class StepZero(Step):
|
||||||
appearance = EnumField(Appearance,
|
pass
|
||||||
required=True,
|
|
||||||
description='Grades the imperfections that aesthetically '
|
|
||||||
'affect the device, but not its usage.')
|
class StepRandom(Step):
|
||||||
appearance_score = Integer(validate=Range(-3, 5), dump_only=True)
|
pass
|
||||||
functionality = EnumField(Functionality,
|
|
||||||
required=True,
|
|
||||||
description='Grades the defects of a device that affect its usage.')
|
class Rate(EventWithOneDevice):
|
||||||
functionality_score = Integer(validate=Range(-3, 5),
|
rating = Integer(validate=Range(*RATE_POSITIVE),
|
||||||
dump_only=True,
|
dump_only=True,
|
||||||
data_key='functionalityScore')
|
data_key='ratingValue',
|
||||||
|
description='The rating for the content.')
|
||||||
|
algorithm_software = EnumField(RatingSoftware,
|
||||||
|
dump_only=True,
|
||||||
|
data_key='algorithmSoftware',
|
||||||
|
description='The algorithm used to produce this rating.')
|
||||||
|
algorithm_version = Version(dump_only=True,
|
||||||
|
data_key='algorithmVersion',
|
||||||
|
description='The algorithm_version of the algorithm_software.')
|
||||||
|
appearance = Integer(validate=Range(-3, 5), dump_only=True)
|
||||||
|
functionality = Integer(validate=Range(-3, 5),
|
||||||
|
dump_only=True,
|
||||||
|
data_key='functionalityScore')
|
||||||
|
|
||||||
|
|
||||||
|
class IndividualRate(Rate):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AggregateRate(Rate):
|
||||||
|
ratings = NestedOn(IndividualRate, many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxRate(IndividualRate):
|
||||||
|
num = Integer(dump_only=True)
|
||||||
|
# todo Image
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxUserRate(PhotoboxRate):
|
||||||
|
assembling = Integer()
|
||||||
|
parts = Integer()
|
||||||
|
buttons = Integer()
|
||||||
|
dents = Integer()
|
||||||
|
decolorization = Integer()
|
||||||
|
scratches = Integer()
|
||||||
|
tag_adhesive = Integer()
|
||||||
|
dirt = Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoboxSystemRate(PhotoboxRate):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WorkbenchRate(IndividualRate):
|
||||||
|
processor = Float()
|
||||||
|
ram = Float()
|
||||||
|
data_storage = Float()
|
||||||
|
graphic_card = Float()
|
||||||
labelling = Boolean(description='Sets if there are labels stuck that should be removed.')
|
labelling = Boolean(description='Sets if there are labels stuck that should be removed.')
|
||||||
bios = EnumField(Bios, description='How difficult it has been to set the bios to '
|
bios = EnumField(Bios, description='How difficult it has been to set the bios to '
|
||||||
'boot from the network.')
|
'boot from the network.')
|
||||||
general = Integer(dump_only=True,
|
appearance_range = EnumField(AppearanceRange,
|
||||||
validate=Range(0, 5),
|
required=True,
|
||||||
description='The grade of the device.')
|
data_key='appearanceRange',
|
||||||
|
description='Grades the imperfections that aesthetically '
|
||||||
|
'affect the device, but not its usage.')
|
||||||
|
functionality_range = EnumField(FunctionalityRange,
|
||||||
|
required=True,
|
||||||
|
data_key='functionalityRange',
|
||||||
|
description='Grades the defects of a device that affect its usage.')
|
||||||
|
|
||||||
|
|
||||||
class Installation(Schema):
|
class Install(EventWithOneDevice):
|
||||||
name = String(validate=Length(STR_BIG_SIZE),
|
name = String(validate=Length(STR_BIG_SIZE),
|
||||||
required=True,
|
required=True,
|
||||||
description='The name of the OS installed.')
|
description='The name of the OS installed.')
|
||||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||||
success = Boolean(required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Inventory(Schema):
|
|
||||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Snapshot(EventWithOneDevice):
|
class Snapshot(EventWithOneDevice):
|
||||||
|
@ -130,57 +174,50 @@ class Snapshot(EventWithOneDevice):
|
||||||
|
|
||||||
See docs for more info.
|
See docs for more info.
|
||||||
"""
|
"""
|
||||||
device = NestedOn(Device) # todo and when dumping?
|
uuid = UUID(required=True)
|
||||||
|
software = EnumField(SnapshotSoftware,
|
||||||
|
required=True,
|
||||||
|
description='The software that generated this Snapshot.')
|
||||||
|
version = Version(required=True, description='The version of the software.')
|
||||||
|
events = NestedOn(Event, many=True) # todo ensure only specific events are submitted
|
||||||
|
expected_events = EnumField(SnapshotExpectedEvents,
|
||||||
|
many=True,
|
||||||
|
data_key='expectedEvents',
|
||||||
|
description='Keep open this Snapshot until the following events'
|
||||||
|
'are performed. Setting this value will activate'
|
||||||
|
'the async Snapshot.')
|
||||||
|
device = NestedOn(Device)
|
||||||
|
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||||
components = NestedOn(Component,
|
components = NestedOn(Component,
|
||||||
many=True,
|
many=True,
|
||||||
description='A list of components that are inside of the device'
|
description='A list of components that are inside of the device'
|
||||||
'at the moment of this Snapshot.'
|
'at the moment of this Snapshot.'
|
||||||
'Order is preserved, so the component num 0 when'
|
'Order is preserved, so the component num 0 when'
|
||||||
'submitting is the component num 0 when returning it back.')
|
'submitting is the component num 0 when returning it back.')
|
||||||
uuid = UUID(required=True)
|
|
||||||
version = Version(required=True, description='The version of the SnapshotSoftware.')
|
|
||||||
software = EnumField(SoftwareType,
|
|
||||||
required=True,
|
|
||||||
description='The software that generated this Snapshot.')
|
|
||||||
condition = Nested(Condition, required=True)
|
|
||||||
install = Nested(Installation)
|
|
||||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
|
||||||
inventory = Nested(Inventory)
|
|
||||||
color = Color(description='Main color of the device.')
|
|
||||||
orientation = EnumField(Orientation, description='Is the device main stand wider or larger?')
|
|
||||||
events = NestedOn(Event, many=True, dump_only=True)
|
|
||||||
|
|
||||||
@validates_schema
|
@validates_schema
|
||||||
def validate_workbench_version(self, data: dict):
|
def validate_workbench_version(self, data: dict):
|
||||||
if data['software'] == SoftwareType.Workbench:
|
if data['software'] == SnapshotSoftware.Workbench:
|
||||||
if data['version'] < app.config['MIN_WORKBENCH']:
|
if data['version'] < app.config['MIN_WORKBENCH']:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
'Min. supported Workbench version is {}'.format(app.config['MIN_WORKBENCH']),
|
'Min. supported Workbench algorithm_version is '
|
||||||
|
'{}'.format(app.config['MIN_WORKBENCH']),
|
||||||
field_names=['version']
|
field_names=['version']
|
||||||
)
|
)
|
||||||
|
|
||||||
@validates_schema
|
@validates_schema
|
||||||
def validate_components_only_workbench(self, data: dict):
|
def validate_components_only_workbench(self, data: dict):
|
||||||
if data['software'] != SoftwareType.Workbench:
|
if data['software'] != SnapshotSoftware.Workbench:
|
||||||
if data['components'] is not None:
|
if data['components'] is not None:
|
||||||
raise ValidationError('Only Workbench can add component info',
|
raise ValidationError('Only Workbench can add component info',
|
||||||
field_names=['components'])
|
field_names=['components'])
|
||||||
|
|
||||||
@post_load
|
|
||||||
def normalize_nested(self, data: dict):
|
|
||||||
data.update(data.pop('condition'))
|
|
||||||
data['condition'] = data.pop('general', None)
|
|
||||||
data.update({'install_' + key: value for key, value in data.pop('install', {})})
|
|
||||||
data['inventory_elapsed'] = data.get('inventory', {}).pop('elapsed', None)
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class Test(EventWithOneDevice):
|
class Test(EventWithOneDevice):
|
||||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||||
success = Boolean(required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class TestHardDrive(Test):
|
class TestDataStorage(Test):
|
||||||
length = EnumField(TestHardDriveLength, required=True)
|
length = EnumField(TestHardDriveLength, required=True)
|
||||||
status = String(validate=Length(max=STR_SIZE), required=True)
|
status = String(validate=Length(max=STR_SIZE), required=True)
|
||||||
lifetime = TimeDelta(precision=TimeDelta.DAYS, required=True)
|
lifetime = TimeDelta(precision=TimeDelta.DAYS, required=True)
|
||||||
|
|
|
@ -4,9 +4,9 @@ from flask import request
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.device.models import Device
|
from ereuse_devicehub.resources.device.models import Computer
|
||||||
from ereuse_devicehub.resources.event.enums import SoftwareType
|
from ereuse_devicehub.resources.enums import SnapshotSoftware
|
||||||
from ereuse_devicehub.resources.event.models import Event, Snapshot, TestHardDrive
|
from ereuse_devicehub.resources.event.models import Event, Snapshot, TestDataStorage
|
||||||
from teal.resource import View
|
from teal.resource import View
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,12 +31,16 @@ class SnapshotView(View):
|
||||||
# Note that if we set the device / components into the snapshot
|
# Note that if we set the device / components into the snapshot
|
||||||
# model object, when we flush them to the db we will flush
|
# model object, when we flush them to the db we will flush
|
||||||
# snapshot, and we want to wait to flush snapshot at the end
|
# snapshot, and we want to wait to flush snapshot at the end
|
||||||
device = s.pop('device') # type: Device
|
device = s.pop('device') # type: Computer
|
||||||
components = s.pop('components') if s['software'] == SoftwareType.Workbench else None
|
components = s.pop('components') if s['software'] == SnapshotSoftware.Workbench else None
|
||||||
|
if 'events' in s:
|
||||||
|
events = s.pop('events')
|
||||||
|
# todo perform events
|
||||||
# noinspection PyArgumentList
|
# noinspection PyArgumentList
|
||||||
snapshot = Snapshot(**s)
|
snapshot = Snapshot(**s)
|
||||||
snapshot.device, snapshot.events = self.resource_def.sync.run(device, components)
|
snapshot.device, snapshot.events = self.resource_def.sync.run(device, components)
|
||||||
snapshot.components = snapshot.device.components
|
snapshot.components = snapshot.device.components
|
||||||
|
# todo compute rating
|
||||||
# commit will change the order of the components by what
|
# commit will change the order of the components by what
|
||||||
# the DB wants. Let's get a copy of the list so we preserve order
|
# the DB wants. Let's get a copy of the list so we preserve order
|
||||||
ordered_components = OrderedSet(x for x in snapshot.components)
|
ordered_components = OrderedSet(x for x in snapshot.components)
|
||||||
|
@ -57,7 +61,7 @@ class TestHardDriveView(View):
|
||||||
def post(self):
|
def post(self):
|
||||||
t = request.get_json() # type: dict
|
t = request.get_json() # type: dict
|
||||||
# noinspection PyArgumentList
|
# noinspection PyArgumentList
|
||||||
test = TestHardDrive(snapshot_id=t.pop('snapshot'), device_id=t.pop('device'), **t)
|
test = TestDataStorage(snapshot_id=t.pop('snapshot'), device_id=t.pop('device'), **t)
|
||||||
return test
|
return test
|
||||||
|
|
||||||
|
|
||||||
|
|
0
ereuse_devicehub/resources/image/__init__.py
Normal file
0
ereuse_devicehub/resources/image/__init__.py
Normal file
42
ereuse_devicehub/resources/image/models.py
Normal file
42
ereuse_devicehub/resources/image/models.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from sqlalchemy import BigInteger, Column, Enum as DBEnum, ForeignKey, Unicode
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
from sqlalchemy.orm import backref, relationship
|
||||||
|
from sqlalchemy.util import OrderedSet
|
||||||
|
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
|
from ereuse_devicehub.resources.enums import ImageMimeTypes, Orientation
|
||||||
|
from ereuse_devicehub.resources.models import STR_BIG_SIZE, Thing
|
||||||
|
from teal.db import CASCADE_OWN
|
||||||
|
|
||||||
|
|
||||||
|
class ImageList(Thing):
|
||||||
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||||
|
device_id = Column(BigInteger, ForeignKey(Device.id), nullable=False)
|
||||||
|
device = relationship(Device,
|
||||||
|
primaryjoin=Device.id == device_id,
|
||||||
|
backref=backref('images',
|
||||||
|
lazy=True,
|
||||||
|
cascade=CASCADE_OWN,
|
||||||
|
order_by=lambda: ImageList.created,
|
||||||
|
collection_class=OrderedSet))
|
||||||
|
|
||||||
|
|
||||||
|
class Image(Thing):
|
||||||
|
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||||
|
name = Column(Unicode(STR_BIG_SIZE), default='', nullable=False)
|
||||||
|
content = db.Column(db.LargeBinary, nullable=False)
|
||||||
|
file_format = db.Column(DBEnum(ImageMimeTypes), nullable=False)
|
||||||
|
orientation = db.Column(DBEnum(Orientation), nullable=False)
|
||||||
|
image_list_id = Column(UUID(as_uuid=True), ForeignKey(ImageList.id), nullable=False)
|
||||||
|
image_list = relationship(ImageList,
|
||||||
|
primaryjoin=ImageList.id == image_list_id,
|
||||||
|
backref=backref('images',
|
||||||
|
cascade=CASCADE_OWN,
|
||||||
|
order_by=lambda: Image.created,
|
||||||
|
collection_class=OrderedSet))
|
||||||
|
|
||||||
|
# todo make an image Field that converts to/from image object
|
||||||
|
# todo which metadata we get from Photobox?
|
41
ereuse_devicehub/resources/image/models.pyi
Normal file
41
ereuse_devicehub/resources/image/models.pyi
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
|
from ereuse_devicehub.resources.enums import ImageMimeTypes, Orientation
|
||||||
|
from ereuse_devicehub.resources.models import Thing
|
||||||
|
|
||||||
|
|
||||||
|
class ImageList(Thing):
|
||||||
|
id = ... # type: Column
|
||||||
|
device = ... # type: Column
|
||||||
|
images = ... # type: relationship
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.id = ... # type: UUID
|
||||||
|
self.device = ... # type: Device
|
||||||
|
self.images = ... # types: List[Image]
|
||||||
|
|
||||||
|
|
||||||
|
class Image(Thing):
|
||||||
|
id = ... # type: Column
|
||||||
|
position = ... #type: Column
|
||||||
|
name = ... # type: Column
|
||||||
|
content = ... # type: Column
|
||||||
|
file_format = ... # type: Column
|
||||||
|
orientation = ... # type: Column
|
||||||
|
image_list = ... # type: relationship
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.id = ... # type: UUID
|
||||||
|
self.position = ... # type: int
|
||||||
|
self.name = '' # type: str
|
||||||
|
self.content = ... # type: bytes
|
||||||
|
self.file_format = ... # type: ImageMimeTypes
|
||||||
|
self.orientation = ... # type: Orientation
|
||||||
|
self.image_list_id = ... # type: UUID
|
||||||
|
self.image_list = ... # type: ImageList
|
|
@ -5,6 +5,7 @@ from ereuse_devicehub.db import db
|
||||||
STR_SIZE = 64
|
STR_SIZE = 64
|
||||||
STR_BIG_SIZE = 128
|
STR_BIG_SIZE = 128
|
||||||
STR_SM_SIZE = 32
|
STR_SM_SIZE = 32
|
||||||
|
STR_XSM_SIZE = 16
|
||||||
|
|
||||||
|
|
||||||
class Thing(db.Model):
|
class Thing(db.Model):
|
||||||
|
|
22
ereuse_devicehub/resources/models.pyi
Normal file
22
ereuse_devicehub/resources/models.pyi
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import Column
|
||||||
|
|
||||||
|
from teal.db import Model
|
||||||
|
|
||||||
|
STR_SIZE = 64
|
||||||
|
STR_BIG_SIZE = 128
|
||||||
|
STR_SM_SIZE = 32
|
||||||
|
STR_XSM_SIZE = 16
|
||||||
|
|
||||||
|
|
||||||
|
class Thing(Model):
|
||||||
|
t = ... # type: str
|
||||||
|
type = ... # type: str
|
||||||
|
updated = ... # type: Column
|
||||||
|
created = ... # type: Column
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.updated = ... # type: datetime
|
||||||
|
self.created = ... # type: datetime
|
|
@ -17,7 +17,7 @@ class Tag(Thing):
|
||||||
# If we link with the Organization object this instance
|
# If we link with the Organization object this instance
|
||||||
# will be set as persistent and added to session
|
# will be set as persistent and added to session
|
||||||
# which is something we don't want to enforce by default
|
# which is something we don't want to enforce by default
|
||||||
default=lambda: Organization.get_default_org().id)
|
default=lambda: Organization.get_default_org_id())
|
||||||
org = relationship(Organization,
|
org = relationship(Organization,
|
||||||
backref=backref('tags', lazy=True),
|
backref=backref('tags', lazy=True),
|
||||||
primaryjoin=Organization.id == org_id,
|
primaryjoin=Organization.id == org_id,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from flask import current_app as app
|
from flask import current_app as app, g
|
||||||
from sqlalchemy import Column, Unicode, UniqueConstraint
|
from sqlalchemy import Column, Unicode, UniqueConstraint
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy_utils import CountryType, EmailType, PasswordType
|
from sqlalchemy_utils import CountryType, EmailType, PasswordType
|
||||||
|
@ -42,9 +42,12 @@ class Organization(Thing):
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_org(cls) -> 'Organization':
|
def get_default_org_id(cls) -> UUID:
|
||||||
"""Retrieves the default organization."""
|
"""Retrieves the default organization."""
|
||||||
return Organization.query.filter_by(**app.config.get_namespace('ORGANIZATION_')).one()
|
return g.setdefault('org_id',
|
||||||
|
Organization.query.filter_by(
|
||||||
|
**app.config.get_namespace('ORGANIZATION_')
|
||||||
|
).one().id)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return '<Org {0.id}: {0.name}>'.format(self)
|
return '<Org {0.id}: {0.name}>'.format(self)
|
||||||
|
|
|
@ -3,7 +3,6 @@ device:
|
||||||
serialNumber: 'p1'
|
serialNumber: 'p1'
|
||||||
model: 'p1'
|
model: 'p1'
|
||||||
type: 'Desktop'
|
type: 'Desktop'
|
||||||
secured: False
|
|
||||||
components:
|
components:
|
||||||
- manufacturer: 'p1c1m'
|
- manufacturer: 'p1c1m'
|
||||||
serialNumber: 'p1c1s'
|
serialNumber: 'p1c1s'
|
||||||
|
@ -18,9 +17,6 @@ components:
|
||||||
serialNumber: 'p1c3s'
|
serialNumber: 'p1c3s'
|
||||||
type: 'GraphicCard'
|
type: 'GraphicCard'
|
||||||
memory: 1.5
|
memory: 1.5
|
||||||
condition:
|
|
||||||
appearance: 'A'
|
|
||||||
functionality: 'B'
|
|
||||||
elapsed: 25
|
elapsed: 25
|
||||||
software: 'Workbench'
|
software: 'Workbench'
|
||||||
uuid: '76860eca-c3fd-41f6-a801-6af7bd8cf832'
|
uuid: '76860eca-c3fd-41f6-a801-6af7bd8cf832'
|
||||||
|
|
|
@ -3,7 +3,6 @@ device:
|
||||||
serialNumber: 'p2s'
|
serialNumber: 'p2s'
|
||||||
model: 'p2'
|
model: 'p2'
|
||||||
type: 'Microtower'
|
type: 'Microtower'
|
||||||
secured: False
|
|
||||||
components:
|
components:
|
||||||
- manufacturer: 'p2c1m'
|
- manufacturer: 'p2c1m'
|
||||||
serialNumber: 'p2c1s'
|
serialNumber: 'p2c1s'
|
||||||
|
@ -14,9 +13,6 @@ components:
|
||||||
speed: 1.23
|
speed: 1.23
|
||||||
cores: 2
|
cores: 2
|
||||||
type: 'Processor'
|
type: 'Processor'
|
||||||
condition:
|
|
||||||
appearance: 'A'
|
|
||||||
functionality: 'B'
|
|
||||||
elapsed: 25
|
elapsed: 25
|
||||||
software: 'Workbench'
|
software: 'Workbench'
|
||||||
uuid: 'f2e02261-87a1-4a50-b9b7-92c0e476e5f2'
|
uuid: 'f2e02261-87a1-4a50-b9b7-92c0e476e5f2'
|
||||||
|
|
|
@ -3,7 +3,6 @@ device:
|
||||||
serialNumber: 'p1'
|
serialNumber: 'p1'
|
||||||
model: 'p1'
|
model: 'p1'
|
||||||
type: 'Desktop'
|
type: 'Desktop'
|
||||||
secured: False
|
|
||||||
components:
|
components:
|
||||||
- manufacturer: 'p1c2m'
|
- manufacturer: 'p1c2m'
|
||||||
serialNumber: 'p1c2s'
|
serialNumber: 'p1c2s'
|
||||||
|
@ -15,9 +14,6 @@ components:
|
||||||
serialNumber: 'p1c3s'
|
serialNumber: 'p1c3s'
|
||||||
type: 'GraphicCard'
|
type: 'GraphicCard'
|
||||||
memory: 1.5
|
memory: 1.5
|
||||||
condition:
|
|
||||||
appearance: 'C'
|
|
||||||
functionality: 'C'
|
|
||||||
elapsed: 30
|
elapsed: 30
|
||||||
software: 'Workbench'
|
software: 'Workbench'
|
||||||
uuid: '3be271b6-5ef4-47d8-8237-5e1133eebfc6'
|
uuid: '3be271b6-5ef4-47d8-8237-5e1133eebfc6'
|
||||||
|
|
|
@ -3,7 +3,6 @@ device:
|
||||||
serialNumber: 'p1'
|
serialNumber: 'p1'
|
||||||
model: 'p1'
|
model: 'p1'
|
||||||
type: 'Desktop'
|
type: 'Desktop'
|
||||||
secured: False
|
|
||||||
components:
|
components:
|
||||||
- manufacturer: 'p1c4m'
|
- manufacturer: 'p1c4m'
|
||||||
serialNumber: 'p1c4s'
|
serialNumber: 'p1c4s'
|
||||||
|
@ -13,9 +12,6 @@ components:
|
||||||
serialNumber: 'p1c3s'
|
serialNumber: 'p1c3s'
|
||||||
type: 'GraphicCard'
|
type: 'GraphicCard'
|
||||||
memory: 1.5
|
memory: 1.5
|
||||||
condition:
|
|
||||||
appearance: 'A'
|
|
||||||
functionality: 'A'
|
|
||||||
elapsed: 25
|
elapsed: 25
|
||||||
software: 'Workbench'
|
software: 'Workbench'
|
||||||
uuid: 'fd007eb4-48e3-454a-8763-169491904c6e'
|
uuid: 'fd007eb4-48e3-454a-8763-169491904c6e'
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
uuid: 'f5efd26e-8754-46bc-87bf-fbccc39d60d9'
|
|
||||||
type: 'Snapshot'
|
type: 'Snapshot'
|
||||||
|
uuid: 'f5efd26e-8754-46bc-87bf-fbccc39d60d9'
|
||||||
version: '11.0'
|
version: '11.0'
|
||||||
software: 'Workbench'
|
software: 'Workbench'
|
||||||
condition:
|
events:
|
||||||
appearance: 'A'
|
- type: 'WorkbenchRate'
|
||||||
functionality: 'B'
|
appearanceRange: 'A'
|
||||||
labelling: True
|
functionalityRange: 'B'
|
||||||
bios: 'B'
|
labelling: True
|
||||||
|
bios: 'B'
|
||||||
elapsed: 4
|
elapsed: 4
|
||||||
device:
|
device:
|
||||||
type: 'Microtower'
|
type: 'Microtower'
|
||||||
|
|
42
tests/files/erase-sectors.snapshot.yaml
Normal file
42
tests/files/erase-sectors.snapshot.yaml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
uuid: '74caa7eb-2bad-4333-94f6-6f1b031d0775'
|
||||||
|
type: 'Snapshot'
|
||||||
|
version: '11.0'
|
||||||
|
software: 'Workbench'
|
||||||
|
elapsed: 4
|
||||||
|
device:
|
||||||
|
type: 'Microtower'
|
||||||
|
serialNumber: 'pc1s'
|
||||||
|
model: 'pc1ml'
|
||||||
|
manufacturer: 'pc1mr'
|
||||||
|
components:
|
||||||
|
- type: 'SolidStateDrive'
|
||||||
|
serialNumber: 'c1s'
|
||||||
|
model: 'c1ml'
|
||||||
|
manufacturer: 'pc1mr'
|
||||||
|
erasure:
|
||||||
|
type: 'EraseSectors'
|
||||||
|
cleanWithZeros: True
|
||||||
|
startTime: '2018-06-01T08:12:06'
|
||||||
|
endTime: '2018-06-01T09:12:06'
|
||||||
|
secureRandomSteps: 20
|
||||||
|
steps:
|
||||||
|
- type: 'StepZero'
|
||||||
|
error: False
|
||||||
|
startTime: '2018-06-01T08:15:00'
|
||||||
|
endTime: '2018-06-01T09:16:00'
|
||||||
|
secureRandomSteps: 1
|
||||||
|
cleanWithZeros: True
|
||||||
|
- type: 'StepZero'
|
||||||
|
error: False
|
||||||
|
startTime: '2018-06-01T08:16:00'
|
||||||
|
endTime: '2018-06-01T09:17:00'
|
||||||
|
secureRandomSteps: 1
|
||||||
|
cleanWithZeros: True
|
||||||
|
- type: 'GraphicCard'
|
||||||
|
serialNumber: 'gc1s'
|
||||||
|
model: 'gc1ml'
|
||||||
|
manufacturer: 'gc1mr'
|
||||||
|
- type: 'RamModule'
|
||||||
|
serialNumber: 'rm1s'
|
||||||
|
model: 'rm1ml'
|
||||||
|
manufacturer: 'rm1mr'
|
87
tests/files/workbench-server-1.snapshot.yaml
Normal file
87
tests/files/workbench-server-1.snapshot.yaml
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
# A Snapshot Phase 1 with a device
|
||||||
|
# and 1 GraphicCard, 2 RamModule, 1 Processor, 1 SSD, 1 HDD, 1 Motherboard
|
||||||
|
# Prerequisites:
|
||||||
|
# - 2 tags: 'tag1' and 'tag2' from the default org
|
||||||
|
# All numbers are invented
|
||||||
|
|
||||||
|
|
||||||
|
type: 'Snapshot'
|
||||||
|
uuid: 'cb8ce6b5-6a1b-4084-b5b9-d8fadad2a015'
|
||||||
|
version: '11.0'
|
||||||
|
software: 'Workbench'
|
||||||
|
startTime: '2018-06-08T17:52:00'
|
||||||
|
expectedEvents: ['TestDataStorage', 'StressTest', 'EraseSectors', 'Install']
|
||||||
|
device:
|
||||||
|
type: 'Microtower'
|
||||||
|
serialNumber: 'd1s'
|
||||||
|
model: 'd1ml'
|
||||||
|
manufacturer: 'd1mr'
|
||||||
|
tags:
|
||||||
|
- type: 'Tag'
|
||||||
|
id: 'tag1'
|
||||||
|
- type: 'Tag'
|
||||||
|
id: 'tag2'
|
||||||
|
events:
|
||||||
|
- type: 'WorkbenchRate'
|
||||||
|
appearanceRange: 'A'
|
||||||
|
functionalityRange: 'B'
|
||||||
|
- type: 'BenchmarkRamSysbench'
|
||||||
|
rate: 2444
|
||||||
|
components:
|
||||||
|
- type: 'GraphicCard'
|
||||||
|
serialNumber: 'gc1-1s'
|
||||||
|
model: 'gc1-1ml'
|
||||||
|
manufacturer: 'gc1-1mr'
|
||||||
|
- type: 'RamModule'
|
||||||
|
serialNumber: 'rm1-1s'
|
||||||
|
model: 'rm1-1ml'
|
||||||
|
manufacturer: 'rm1-1mr'
|
||||||
|
- type: 'RamModule'
|
||||||
|
serialNumber: 'rm2-1s'
|
||||||
|
model: 'rm2-1ml'
|
||||||
|
manufacturer: 'rm2-1mr'
|
||||||
|
- type: 'Processor'
|
||||||
|
model: 'p1-1s'
|
||||||
|
manufacturer: 'p1-1mr'
|
||||||
|
benchmarks:
|
||||||
|
- type: 'BenchmarkProcessor'
|
||||||
|
rate: 2410
|
||||||
|
- type: 'BenchmarkProcessorSysbench'
|
||||||
|
rate: 4400
|
||||||
|
- type: 'SolidStateDrive'
|
||||||
|
serialNumber: 'ssd1-1s'
|
||||||
|
model: 'ssd1-1ml'
|
||||||
|
manufacturer: 'ssd1-1mr'
|
||||||
|
benchmark:
|
||||||
|
type: 'BenchmarkDataStorage'
|
||||||
|
readingSpeed: 20
|
||||||
|
writingSpeed: 15
|
||||||
|
test:
|
||||||
|
type: 'TestDataStorage'
|
||||||
|
firstError: 0
|
||||||
|
error: False
|
||||||
|
status: 'Completed without error'
|
||||||
|
length: 'Short'
|
||||||
|
lifetime: 99
|
||||||
|
passedLifeTime: 99
|
||||||
|
assessment: True
|
||||||
|
powerCycleCount: 11
|
||||||
|
reallocatedSectorCount: 2
|
||||||
|
powerCycleCount: 4
|
||||||
|
reportedUncorrectableErrors: 1
|
||||||
|
commandTimeout: 11
|
||||||
|
currentPendingSectorCount: 1
|
||||||
|
offlineUncorrectable: 33
|
||||||
|
remainingLifetimePercentage: 1
|
||||||
|
- type: 'HardDrive'
|
||||||
|
serialNumber: 'hdd1-1s'
|
||||||
|
model: 'hdd1-1ml'
|
||||||
|
manufacturer: 'hdd1-1mr'
|
||||||
|
benchmark:
|
||||||
|
type: 'BenchmarkDataStorage'
|
||||||
|
readingSpeed: 10
|
||||||
|
writingSpeed: 5
|
||||||
|
- type: 'Motherboard'
|
||||||
|
serialNumber: 'mb1-1s'
|
||||||
|
model: 'mb1-1ml'
|
||||||
|
manufacturer: 'mb1-1mr'
|
12
tests/files/workbench-server-2.stress-test.yaml
Normal file
12
tests/files/workbench-server-2.stress-test.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# A Snapshot Phase 1 with a device
|
||||||
|
# and 1 GraphicCard, 2 RamModule, 1 Processor, 1 SSD, 1 HDD, 1 Motherboard
|
||||||
|
# Prerequisites:
|
||||||
|
# - workbench-server-1.snapshot.yaml
|
||||||
|
# Requisites:
|
||||||
|
# - Set the ``snapshot`` field to ``workbench-server-1...``
|
||||||
|
# All numbers are invented
|
||||||
|
|
||||||
|
type: 'StressTest'
|
||||||
|
elapsed: 300
|
||||||
|
error: False
|
||||||
|
snapshot: None # fulfill!
|
21
tests/files/workbench-server-3.erase.yaml
Normal file
21
tests/files/workbench-server-3.erase.yaml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# A Snapshot Phase 1 with a device
|
||||||
|
# and 1 GraphicCard, 2 RamModule, 1 Processor, 1 SSD, 1 HDD, 1 Motherboard
|
||||||
|
# Prerequisites:
|
||||||
|
# - workbench-server-1.snapshot.yaml
|
||||||
|
# Requisites:
|
||||||
|
# - Set the ``snapshot`` field to ``workbench-server-1...``
|
||||||
|
# All numbers are invented
|
||||||
|
|
||||||
|
type: 'EraseSectors'
|
||||||
|
error: False
|
||||||
|
snapshot: None # fulfill!
|
||||||
|
device: None # fulfill!
|
||||||
|
cleanWithZeros: False
|
||||||
|
startTime: 2018-01-01T10:10:10
|
||||||
|
endTime: 2018-01-01T12:10:10
|
||||||
|
secureRandomSteps: 0
|
||||||
|
steps:
|
||||||
|
- type: 'StepRandom'
|
||||||
|
startTime: '2018-01-01T10:10:10'
|
||||||
|
endTime: '2018-01-01T12:10:10'
|
||||||
|
error: False
|
14
tests/files/workbench-server-4.install.yaml
Normal file
14
tests/files/workbench-server-4.install.yaml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# A Snapshot Phase 1 with a device
|
||||||
|
# and 1 GraphicCard, 2 RamModule, 1 Processor, 1 SSD, 1 HDD, 1 Motherboard
|
||||||
|
# Prerequisites:
|
||||||
|
# - workbench-server-1.snapshot.yaml
|
||||||
|
# Requisites:
|
||||||
|
# - Set the ``snapshot`` field to ``workbench-server-1...``
|
||||||
|
# All numbers are invented
|
||||||
|
|
||||||
|
type: 'Install'
|
||||||
|
elapsed: 420
|
||||||
|
error: False
|
||||||
|
snapshot: None # fulfill!
|
||||||
|
device: None # fulfill!
|
||||||
|
name: 'LinuxMint 18.01 32b'
|
|
@ -2,6 +2,8 @@ from datetime import timedelta
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from colour import Color
|
||||||
|
|
||||||
from ereuse_utils.naming import Naming
|
from ereuse_utils.naming import Naming
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
|
@ -79,15 +81,13 @@ def test_physical_properties():
|
||||||
model='ml',
|
model='ml',
|
||||||
manufacturer='mr',
|
manufacturer='mr',
|
||||||
width=2.0,
|
width=2.0,
|
||||||
pid='abc')
|
color=Color())
|
||||||
pc = Computer()
|
pc = Computer()
|
||||||
pc.components.add(c)
|
pc.components.add(c)
|
||||||
db.session.add(pc)
|
db.session.add(pc)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
assert c.physical_properties == {
|
assert c.physical_properties == {
|
||||||
'gid': None,
|
|
||||||
'usb': 3,
|
'usb': 3,
|
||||||
'pid': 'abc',
|
|
||||||
'serial_number': 'sn',
|
'serial_number': 'sn',
|
||||||
'pcmcia': None,
|
'pcmcia': None,
|
||||||
'model': 'ml',
|
'model': 'ml',
|
||||||
|
@ -97,7 +97,9 @@ def test_physical_properties():
|
||||||
'manufacturer': 'mr',
|
'manufacturer': 'mr',
|
||||||
'weight': None,
|
'weight': None,
|
||||||
'height': None,
|
'height': None,
|
||||||
'width': 2.0
|
'width': 2.0,
|
||||||
|
'color': Color(),
|
||||||
|
'depth': None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -358,16 +360,15 @@ def test_get_device(app: Devicehub, user: UserClient):
|
||||||
db.session.add(pc)
|
db.session.add(pc)
|
||||||
db.session.add(Test(device=pc,
|
db.session.add(Test(device=pc,
|
||||||
elapsed=timedelta(seconds=4),
|
elapsed=timedelta(seconds=4),
|
||||||
success=True,
|
error=False,
|
||||||
author=User(email='bar@bar.com')))
|
author=User(email='bar@bar.com')))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
pc, _ = user.get(res=Device, item=1)
|
pc, _ = user.get(res=Device, item=1)
|
||||||
assert len(pc['events']) == 1
|
assert len(pc['events']) == 1
|
||||||
assert pc['events'][0]['type'] == 'Test'
|
assert pc['events'][0]['type'] == 'Test'
|
||||||
assert pc['events'][0]['id'] == 1
|
|
||||||
assert pc['events'][0]['device'] == 1
|
assert pc['events'][0]['device'] == 1
|
||||||
assert pc['events'][0]['elapsed'] == 4
|
assert pc['events'][0]['elapsed'] == 4
|
||||||
assert pc['events'][0]['success'] == True
|
assert not pc['events'][0]['error']
|
||||||
assert UUID(pc['events'][0]['author'])
|
assert UUID(pc['events'][0]['author'])
|
||||||
assert 'events_components' not in pc, 'events_components are internal use only'
|
assert 'events_components' not in pc, 'events_components are internal use only'
|
||||||
assert 'events_one' not in pc, 'they are internal use only'
|
assert 'events_one' not in pc, 'they are internal use only'
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from flask import g
|
from flask import g
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.device.models import Device
|
from ereuse_devicehub.resources.device.models import Device, GraphicCard, HardDrive, \
|
||||||
from ereuse_devicehub.resources.event.models import EventWithOneDevice
|
SolidStateDrive
|
||||||
|
from ereuse_devicehub.resources.enums import TestHardDriveLength
|
||||||
|
from ereuse_devicehub.resources.event.models import EraseBasic, EraseSectors, \
|
||||||
|
EventWithOneDevice, Install, StepZero, TestDataStorage
|
||||||
from tests.conftest import create_user
|
from tests.conftest import create_user
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,3 +27,101 @@ def test_author():
|
||||||
assert e.author_id is None
|
assert e.author_id is None
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
assert e.author == user
|
assert e.author == user
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('auth_app_context')
|
||||||
|
def test_erase_basic():
|
||||||
|
erasure = EraseBasic(
|
||||||
|
device=HardDrive(serial_number='foo', manufacturer='bar', model='foo-bar'),
|
||||||
|
clean_with_zeros=True,
|
||||||
|
start_time=datetime.now(),
|
||||||
|
end_time=datetime.now(),
|
||||||
|
secure_random_steps=25,
|
||||||
|
error=False
|
||||||
|
)
|
||||||
|
db.session.add(erasure)
|
||||||
|
db.session.commit()
|
||||||
|
db_erasure = EraseBasic.query.one()
|
||||||
|
assert erasure == db_erasure
|
||||||
|
assert next(iter(db_erasure.device.events)) == erasure
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('auth_app_context')
|
||||||
|
def test_validate_device_data_storage():
|
||||||
|
"""Checks the validation for data-storage-only events works."""
|
||||||
|
# We can't set a GraphicCard
|
||||||
|
with pytest.raises(TypeError,
|
||||||
|
message='EraseBasic.device must be a DataStorage '
|
||||||
|
'but you passed <GraphicCard None model=\'foo-bar\' S/N=\'foo\'>'):
|
||||||
|
EraseBasic(
|
||||||
|
device=GraphicCard(serial_number='foo', manufacturer='bar', model='foo-bar'),
|
||||||
|
clean_with_zeros=True,
|
||||||
|
start_time=datetime.now(),
|
||||||
|
end_time=datetime.now(),
|
||||||
|
secure_random_steps=25,
|
||||||
|
error=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('auth_app_context')
|
||||||
|
def test_erase_sectors_steps():
|
||||||
|
erasure = EraseSectors(
|
||||||
|
device=SolidStateDrive(serial_number='foo', manufacturer='bar', model='foo-bar'),
|
||||||
|
clean_with_zeros=True,
|
||||||
|
start_time=datetime.now(),
|
||||||
|
end_time=datetime.now(),
|
||||||
|
secure_random_steps=25,
|
||||||
|
error=False,
|
||||||
|
steps=[
|
||||||
|
StepZero(error=False,
|
||||||
|
start_time=datetime.now(),
|
||||||
|
end_time=datetime.now(),
|
||||||
|
secure_random_steps=1,
|
||||||
|
clean_with_zeros=True),
|
||||||
|
StepZero(error=False,
|
||||||
|
start_time=datetime.now(),
|
||||||
|
end_time=datetime.now(),
|
||||||
|
secure_random_steps=2,
|
||||||
|
clean_with_zeros=True),
|
||||||
|
StepZero(error=False,
|
||||||
|
start_time=datetime.now(),
|
||||||
|
end_time=datetime.now(),
|
||||||
|
secure_random_steps=3,
|
||||||
|
clean_with_zeros=True)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
db.session.add(erasure)
|
||||||
|
db.session.commit()
|
||||||
|
db_erasure = EraseSectors.query.one()
|
||||||
|
# Steps are in order
|
||||||
|
assert db_erasure.steps[0].secure_random_steps == 1
|
||||||
|
assert db_erasure.steps[0].num == 0
|
||||||
|
assert db_erasure.steps[1].secure_random_steps == 2
|
||||||
|
assert db_erasure.steps[1].num == 1
|
||||||
|
assert db_erasure.steps[2].secure_random_steps == 3
|
||||||
|
assert db_erasure.steps[2].num == 2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('auth_app_context')
|
||||||
|
def test_test_data_storage():
|
||||||
|
test = TestDataStorage(
|
||||||
|
device=HardDrive(serial_number='foo', manufacturer='bar', model='foo-bar'),
|
||||||
|
error=False,
|
||||||
|
elapsed=timedelta(minutes=25),
|
||||||
|
length=TestHardDriveLength.Short,
|
||||||
|
status='OK!',
|
||||||
|
lifetime=timedelta(days=120)
|
||||||
|
)
|
||||||
|
db.session.add(test)
|
||||||
|
db.session.commit()
|
||||||
|
assert TestDataStorage.query.one()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('auth_app_context')
|
||||||
|
def test_install():
|
||||||
|
hdd = HardDrive(serial_number='sn')
|
||||||
|
install = Install(name='LinuxMint 18.04 es',
|
||||||
|
elapsed=timedelta(seconds=25),
|
||||||
|
device=hdd)
|
||||||
|
db.session.add(install)
|
||||||
|
db.session.commit()
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ereuse_devicehub.config import DevicehubConfig
|
from ereuse_devicehub.config import DevicehubConfig
|
||||||
|
@ -13,4 +15,4 @@ def test_default_org_exists(config: DevicehubConfig):
|
||||||
"""
|
"""
|
||||||
assert Organization.query.filter_by(name=config.ORGANIZATION_NAME,
|
assert Organization.query.filter_by(name=config.ORGANIZATION_NAME,
|
||||||
tax_id=config.ORGANIZATION_TAX_ID).one()
|
tax_id=config.ORGANIZATION_TAX_ID).one()
|
||||||
assert Organization.get_default_org().name == config.ORGANIZATION_NAME
|
assert isinstance(Organization.get_default_org_id(), UUID)
|
||||||
|
|
40
tests/test_rate.py
Normal file
40
tests/test_rate.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from distutils.version import StrictVersion
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
from ereuse_devicehub.resources.device.models import Microtower
|
||||||
|
from ereuse_devicehub.resources.enums import Bios, ImageMimeTypes, Orientation, RatingSoftware
|
||||||
|
from ereuse_devicehub.resources.event.models import PhotoboxRate, WorkbenchRate
|
||||||
|
from ereuse_devicehub.resources.image.models import Image, ImageList
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('auth_app_context')
|
||||||
|
def test_workbench_rate():
|
||||||
|
rate = WorkbenchRate(processor=0.1,
|
||||||
|
ram=1.0,
|
||||||
|
bios=Bios.A,
|
||||||
|
labelling=False,
|
||||||
|
graphic_card=0.1,
|
||||||
|
data_storage=4.1,
|
||||||
|
algorithm_software=RatingSoftware.Ereuse,
|
||||||
|
algorithm_version=StrictVersion('1.0'),
|
||||||
|
device=Microtower(serial_number='24'))
|
||||||
|
db.session.add(rate)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('auth_app_context')
|
||||||
|
def test_photobox_rate():
|
||||||
|
pc = Microtower(serial_number='24')
|
||||||
|
image = Image(name='foo',
|
||||||
|
content=b'123',
|
||||||
|
file_format=ImageMimeTypes.jpg,
|
||||||
|
orientation=Orientation.Horizontal,
|
||||||
|
image_list=ImageList(device=pc))
|
||||||
|
rate = PhotoboxRate(image=image,
|
||||||
|
algorithm_software=RatingSoftware.Ereuse,
|
||||||
|
algorithm_version=StrictVersion('1.0'),
|
||||||
|
device=pc)
|
||||||
|
db.session.add(rate)
|
||||||
|
db.session.commit()
|
|
@ -1,5 +1,6 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import List
|
from distutils.version import StrictVersion
|
||||||
|
from typing import List, Tuple
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -8,10 +9,11 @@ from ereuse_devicehub.client import UserClient
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.devicehub import Devicehub
|
from ereuse_devicehub.devicehub import Devicehub
|
||||||
from ereuse_devicehub.resources.device.exceptions import NeedsId
|
from ereuse_devicehub.resources.device.exceptions import NeedsId
|
||||||
from ereuse_devicehub.resources.device.models import Device, Microtower
|
from ereuse_devicehub.resources.device.models import Device, Microtower, SolidStateDrive
|
||||||
from ereuse_devicehub.resources.device.sync import MismatchBetweenTagsAndHid
|
from ereuse_devicehub.resources.device.sync import MismatchBetweenTagsAndHid
|
||||||
from ereuse_devicehub.resources.event.models import Appearance, Bios, Event, Functionality, \
|
from ereuse_devicehub.resources.enums import Bios, RatingSoftware, SnapshotSoftware
|
||||||
Snapshot, SnapshotRequest, SoftwareType
|
from ereuse_devicehub.resources.event.models import EraseBasic, Event, Snapshot, SnapshotRequest, \
|
||||||
|
WorkbenchRate
|
||||||
from ereuse_devicehub.resources.tag import Tag
|
from ereuse_devicehub.resources.tag import Tag
|
||||||
from ereuse_devicehub.resources.user.models import User
|
from ereuse_devicehub.resources.user.models import User
|
||||||
from tests.conftest import file
|
from tests.conftest import file
|
||||||
|
@ -19,7 +21,8 @@ from tests.conftest import file
|
||||||
|
|
||||||
def assert_similar_device(device1: dict, device2: dict):
|
def assert_similar_device(device1: dict, device2: dict):
|
||||||
"""
|
"""
|
||||||
Like Model.is_similar() but adapted for testing.
|
Like :class:`ereuse_devicehub.resources.device.models.Device.
|
||||||
|
is_similar()` but adapted for testing.
|
||||||
"""
|
"""
|
||||||
assert isinstance(device1, dict) and device1
|
assert isinstance(device1, dict) and device1
|
||||||
assert isinstance(device2, dict) and device2
|
assert isinstance(device2, dict) and device2
|
||||||
|
@ -39,10 +42,21 @@ def assert_similar_components(components1: List[dict], components2: List[dict]):
|
||||||
|
|
||||||
def snapshot_and_check(user: UserClient,
|
def snapshot_and_check(user: UserClient,
|
||||||
input_snapshot: dict,
|
input_snapshot: dict,
|
||||||
event_types: tuple or list = tuple(),
|
event_types: Tuple[str] = tuple(),
|
||||||
perform_second_snapshot=True) -> dict:
|
perform_second_snapshot=True) -> dict:
|
||||||
"""
|
"""
|
||||||
P
|
Performs a Snapshot and then checks if the result is ok:
|
||||||
|
|
||||||
|
- There have been performed the types of events and in the same
|
||||||
|
order as described in the passed-in ``event_types``.
|
||||||
|
- The inputted devices are similar to the resulted ones.
|
||||||
|
- There is no Remove event after the first Add.
|
||||||
|
- All input components are now inside the parent device.
|
||||||
|
|
||||||
|
Optionally, it can perform a second Snapshot which should
|
||||||
|
perform an exact result, except for the events.
|
||||||
|
|
||||||
|
:return: The last resulting snapshot.
|
||||||
"""
|
"""
|
||||||
snapshot, _ = user.post(res=Snapshot, data=input_snapshot)
|
snapshot, _ = user.post(res=Snapshot, data=input_snapshot)
|
||||||
assert tuple(e['type'] for e in snapshot['events']) == event_types
|
assert tuple(e['type'] for e in snapshot['events']) == event_types
|
||||||
|
@ -76,22 +90,25 @@ def test_snapshot_model():
|
||||||
snapshot = Snapshot(uuid=uuid4(),
|
snapshot = Snapshot(uuid=uuid4(),
|
||||||
date=datetime.now(),
|
date=datetime.now(),
|
||||||
version='1.0',
|
version='1.0',
|
||||||
software=SoftwareType.DesktopApp,
|
software=SnapshotSoftware.DesktopApp,
|
||||||
appearance=Appearance.A,
|
|
||||||
appearance_score=5,
|
|
||||||
functionality=Functionality.A,
|
|
||||||
functionality_score=5,
|
|
||||||
labelling=False,
|
|
||||||
bios=Bios.C,
|
|
||||||
condition=5,
|
|
||||||
elapsed=timedelta(seconds=25))
|
elapsed=timedelta(seconds=25))
|
||||||
snapshot.device = device
|
snapshot.device = device
|
||||||
snapshot.request = SnapshotRequest(request={'foo': 'bar'})
|
snapshot.request = SnapshotRequest(request={'foo': 'bar'})
|
||||||
|
snapshot.events.add(WorkbenchRate(processor=0.1,
|
||||||
|
ram=1.0,
|
||||||
|
bios=Bios.A,
|
||||||
|
labelling=False,
|
||||||
|
graphic_card=0.1,
|
||||||
|
data_storage=4.1,
|
||||||
|
algorithm_software=RatingSoftware.Ereuse,
|
||||||
|
algorithm_version=StrictVersion('1.0'),
|
||||||
|
device=device))
|
||||||
db.session.add(snapshot)
|
db.session.add(snapshot)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
device = Microtower.query.one() # type: Microtower
|
device = Microtower.query.one() # type: Microtower
|
||||||
assert device.events_one[0].type == Snapshot.__name__
|
e1, e2 = device.events
|
||||||
|
assert isinstance(e1, Snapshot), 'Creation order must be preserved: 1. snapshot, 2. WR'
|
||||||
|
assert isinstance(e2, WorkbenchRate)
|
||||||
db.session.delete(device)
|
db.session.delete(device)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
assert Snapshot.query.one_or_none() is None
|
assert Snapshot.query.one_or_none() is None
|
||||||
|
@ -137,7 +154,7 @@ def test_snapshot_component_add_remove(user: UserClient):
|
||||||
[c['serialNumber'] for c in e['components']],
|
[c['serialNumber'] for c in e['components']],
|
||||||
e.get('snapshot', {}).get('id', None)
|
e.get('snapshot', {}).get('id', None)
|
||||||
)
|
)
|
||||||
for e in (user.get(res=Event, item=e['id'])[0] for e in events)
|
for e in user.get_many(res=Event, resources=events, key='id')
|
||||||
)
|
)
|
||||||
|
|
||||||
# We add the first device (2 times). The distribution of components
|
# We add the first device (2 times). The distribution of components
|
||||||
|
@ -255,7 +272,7 @@ def _test_snapshot_computer_no_hid(user: UserClient):
|
||||||
|
|
||||||
def test_snapshot_mismatch_id():
|
def test_snapshot_mismatch_id():
|
||||||
"""Tests uploading a device with an ID from another device."""
|
"""Tests uploading a device with an ID from another device."""
|
||||||
# Note that this won't happen as in this new version
|
# Note that this won't happen as in this new algorithm_version
|
||||||
# the ID is not used in the Snapshot process
|
# the ID is not used in the Snapshot process
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -279,3 +296,30 @@ def test_snapshot_tag_inner_tag_mismatch_between_tags_and_hid(user: UserClient,
|
||||||
user.post(pc2, res=Snapshot) # PC2 uploads well
|
user.post(pc2, res=Snapshot) # PC2 uploads well
|
||||||
pc2['device']['tags'] = [{'type': 'Tag', 'id': tag_id}] # Set tag from pc1 to pc2
|
pc2['device']['tags'] = [{'type': 'Tag', 'id': tag_id}] # Set tag from pc1 to pc2
|
||||||
user.post(pc2, res=Snapshot, status=MismatchBetweenTagsAndHid)
|
user.post(pc2, res=Snapshot, status=MismatchBetweenTagsAndHid)
|
||||||
|
|
||||||
|
|
||||||
|
def test_erase(user: UserClient):
|
||||||
|
"""Tests a Snapshot with EraseSectors."""
|
||||||
|
s = file('erase-sectors.snapshot')
|
||||||
|
snapshot = snapshot_and_check(user, s, ('EraseSectors',), perform_second_snapshot=True)
|
||||||
|
storage, *_ = snapshot['components']
|
||||||
|
assert storage['type'] == 'SolidStateDrive', 'Components must be ordered by input order'
|
||||||
|
storage, _ = user.get(res=SolidStateDrive, item=storage['id']) # Let's get storage events too
|
||||||
|
_snapshot1, _snapshot2, erasure = storage['events']
|
||||||
|
assert erasure['type'] == 'EraseSectors'
|
||||||
|
assert _snapshot1['type'] == _snapshot2['type'] == 'Snapshot'
|
||||||
|
assert snapshot == _snapshot2
|
||||||
|
erasure, _ = user.get(res=EraseBasic, item=erasure['id'])
|
||||||
|
assert len(erasure['steps']) == 2
|
||||||
|
assert erasure['steps'][0]['startingTime'] == '2018-06-01T08:15:00'
|
||||||
|
assert erasure['steps'][0]['endingTime'] == '2018-06-01T09:16:00'
|
||||||
|
assert erasure['steps'][1]['endingTime'] == '2018-06-01T08:16:00'
|
||||||
|
assert erasure['steps'][1]['endingTime'] == '2018-06-01T09:17:00'
|
||||||
|
assert erasure['device']['id'] == storage['id']
|
||||||
|
for step in erasure['steps']:
|
||||||
|
assert step['type'] == 'StepZero'
|
||||||
|
assert step['error'] is False
|
||||||
|
assert step['secureRandomSteps'] == 1
|
||||||
|
assert step['cleanWithZeros'] is True
|
||||||
|
assert 'num' not in step
|
||||||
|
assert step['erasure'] == erasure['id']
|
||||||
|
|
115
tests/test_workbench.py
Normal file
115
tests/test_workbench.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
"""
|
||||||
|
Tests that emulates the behaviour of a WorkbenchServer.
|
||||||
|
"""
|
||||||
|
from ereuse_devicehub.client import UserClient
|
||||||
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
|
from ereuse_devicehub.resources.event.models import EraseSectors, Install, Snapshot, \
|
||||||
|
StressTest
|
||||||
|
from tests.conftest import file
|
||||||
|
|
||||||
|
|
||||||
|
def test_workbench_server_phases(user: UserClient):
|
||||||
|
"""
|
||||||
|
Tests the phases described in the docs section `Snapshots from
|
||||||
|
Workbench <http://devicehub.ereuse.org/
|
||||||
|
events.html#snapshots-from-workbench>`_.
|
||||||
|
"""
|
||||||
|
# 1. Snapshot with sync / rate / benchmarks / test data storage
|
||||||
|
s = file('workbench-server-1.snapshot')
|
||||||
|
snapshot, _ = user.post(res=Snapshot, data=s)
|
||||||
|
assert not snapshot['closed'], 'Snapshot must be waiting for the new events'
|
||||||
|
|
||||||
|
# 2. stress test
|
||||||
|
st = file('workbench-server-2.stress-test')
|
||||||
|
st['snapshot'] = snapshot['id']
|
||||||
|
stress_test, _ = user.post(res=StressTest, data=st)
|
||||||
|
|
||||||
|
# 3. erase
|
||||||
|
ssd_id, hdd_id = snapshot['components'][4]['id'], snapshot['components'][5]['id']
|
||||||
|
e = file('workbench-server-3.erase')
|
||||||
|
e['snapshot'], e['device'] = snapshot['id'], ssd_id
|
||||||
|
erase1, _ = user.post(res=EraseSectors, data=e)
|
||||||
|
|
||||||
|
# 3 bis. a second erase
|
||||||
|
e = file('workbench-server-3.erase')
|
||||||
|
e['snapshot'], e['device'] = snapshot['id'], hdd_id
|
||||||
|
erase2, _ = user.post(res=EraseSectors, data=e)
|
||||||
|
|
||||||
|
# 4. Install
|
||||||
|
i = file('workbench-server-4.install')
|
||||||
|
i['snapshot'], i['device'] = snapshot['id'], ssd_id
|
||||||
|
install, _ = user.post(res=Install, data=i)
|
||||||
|
|
||||||
|
# Check events have been appended in Snapshot and devices
|
||||||
|
# and that Snapshot is closed
|
||||||
|
snapshot, _ = user.get(res=Snapshot, item=snapshot['id'])
|
||||||
|
events = snapshot['events']
|
||||||
|
assert len(events) == 9
|
||||||
|
assert events[0]['type'] == 'Rate'
|
||||||
|
assert events[0]['device'] == 1
|
||||||
|
assert events[0]['closed']
|
||||||
|
assert events[0]['type'] == 'WorkbenchRate'
|
||||||
|
assert events[0]['device'] == 1
|
||||||
|
assert events[1]['type'] == 'BenchmarkProcessor'
|
||||||
|
assert events[1]['device'] == 5
|
||||||
|
assert events[2]['type'] == 'BenchmarkProcessorSysbench'
|
||||||
|
assert events[2]['device'] == 5
|
||||||
|
assert events[3]['type'] == 'BenchmarkDataStorage'
|
||||||
|
assert events[3]['device'] == 6
|
||||||
|
assert events[4]['type'] == 'TestDataStorage'
|
||||||
|
assert events[4]['device'] == 6
|
||||||
|
assert events[4]['type'] == 'BenchmarkDataStorage'
|
||||||
|
assert events[4]['device'] == 7
|
||||||
|
assert events[5]['type'] == 'StressTest'
|
||||||
|
assert events[5]['device'] == 1
|
||||||
|
assert events[6]['type'] == 'EraseSectors'
|
||||||
|
assert events[6]['device'] == 6
|
||||||
|
assert events[7]['type'] == 'EraseSectors'
|
||||||
|
assert events[7]['device'] == 7
|
||||||
|
assert events[8]['type'] == 'Install'
|
||||||
|
assert events[8]['device'] == 6
|
||||||
|
assert snapshot['closed']
|
||||||
|
assert not snapshot['error']
|
||||||
|
|
||||||
|
pc, _ = user.get(res=Device, item=snapshot['id'])
|
||||||
|
assert len(pc['events']) == 10 # todo shall I add child events?
|
||||||
|
|
||||||
|
|
||||||
|
def test_workbench_server_condensed(user: UserClient):
|
||||||
|
"""
|
||||||
|
As :def:`.test_workbench_server_phases` but all the events
|
||||||
|
condensed in only one big ``Snapshot`` file, as described
|
||||||
|
in the docs.
|
||||||
|
"""
|
||||||
|
s = file('workbench-server-1.snapshot')
|
||||||
|
s['events'].append(file('workbench-server-2.stress-test'))
|
||||||
|
s['components'][5]['erasure'] = file('workbench-server-3.erase')
|
||||||
|
s['components'][5]['installation'] = file('workbench-server-4.install')
|
||||||
|
s['components'][6]['erasure'] = file('workbench-server-3.erase')
|
||||||
|
snapshot, _ = user.post(res=Snapshot, data=s)
|
||||||
|
events = snapshot['events']
|
||||||
|
assert events[0]['type'] == 'Rate'
|
||||||
|
assert events[0]['device'] == 1
|
||||||
|
assert events[0]['closed']
|
||||||
|
assert events[0]['type'] == 'WorkbenchRate'
|
||||||
|
assert events[0]['device'] == 1
|
||||||
|
assert events[1]['type'] == 'BenchmarkProcessor'
|
||||||
|
assert events[1]['device'] == 5
|
||||||
|
assert events[2]['type'] == 'BenchmarkProcessorSysbench'
|
||||||
|
assert events[2]['device'] == 5
|
||||||
|
assert events[3]['type'] == 'BenchmarkDataStorage'
|
||||||
|
assert events[3]['device'] == 6
|
||||||
|
assert events[4]['type'] == 'TestDataStorage'
|
||||||
|
assert events[4]['device'] == 6
|
||||||
|
assert events[4]['type'] == 'BenchmarkDataStorage'
|
||||||
|
assert events[4]['device'] == 7
|
||||||
|
assert events[5]['type'] == 'StressTest'
|
||||||
|
assert events[5]['device'] == 1
|
||||||
|
assert events[6]['type'] == 'EraseSectors'
|
||||||
|
assert events[6]['device'] == 6
|
||||||
|
assert events[7]['type'] == 'EraseSectors'
|
||||||
|
assert events[7]['device'] == 7
|
||||||
|
assert events[8]['type'] == 'Install'
|
||||||
|
assert events[8]['device'] == 6
|
||||||
|
assert snapshot['closed']
|
||||||
|
assert not snapshot['error']
|
Reference in a new issue