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.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
SPHINXPROJ = Devicehub
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
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
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@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"
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
rm -rf modules
|
||||
.PHONY: help Makefile
|
||||
|
||||
.PHONY: html
|
||||
html:
|
||||
sphinx-apidoc -f -l -o modules ../ereuse_devicehub
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@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."
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
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 -*-
|
||||
#
|
||||
# DeviceHub documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon Apr 18 16:40:20 2016.
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# 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.
|
||||
# This file does only contain a selection of the most common options. For a
|
||||
# full list see the documentation:
|
||||
# http://www.sphinx-doc.org/en/master/config
|
||||
|
||||
import os
|
||||
import sys
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# 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
|
||||
# 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.
|
||||
needs_sphinx = '1.4.7'
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinxcontrib.httpdomain',
|
||||
'sphinx.ext.todo'
|
||||
'sphinxcontrib.plantuml'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
@ -43,29 +49,13 @@ templates_path = ['_templates']
|
|||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
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
|
||||
# for a list of supported languages.
|
||||
#
|
||||
|
@ -73,157 +63,65 @@ release = '0.1'
|
|||
# Usually you set "language" from the command line for these cases.
|
||||
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
|
||||
# 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']
|
||||
|
||||
# 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.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# 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 ----------------------------------------------
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# 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
|
||||
# documentation.
|
||||
#
|
||||
# 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,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
# html_extra_path = []
|
||||
|
||||
# If not None, a 'Last updated on:' timestamp is inserted at every page
|
||||
# bottom, using the given strftime format.
|
||||
# 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.
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# The default sidebars (for documents that don't match any pattern) are
|
||||
# defined by theme itself. Builtin themes are using these templates by
|
||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||
# 'searchbox.html']``.
|
||||
#
|
||||
# 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.
|
||||
# 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'
|
||||
# -- Options for HTMLHelp output ---------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'DeviceHubdoc'
|
||||
htmlhelp_basename = 'Devicehubdoc'
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
# -- Options for LaTeX output ------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
|
@ -231,70 +129,44 @@ latex_elements = {
|
|||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'DeviceHub.tex', 'DeviceHub Documentation',
|
||||
(master_doc, 'Devicehub.tex', 'Devicehub Documentation',
|
||||
'eReuse.org team', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# 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 ---------------------------------------
|
||||
# -- Options for manual page output ------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'devicehub', 'DeviceHub Documentation',
|
||||
(master_doc, 'devicehub', 'Devicehub Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
# -- Options for Texinfo output ----------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'DeviceHub', 'DeviceHub Documentation',
|
||||
author, 'DeviceHub', 'One line description of project.',
|
||||
(master_doc, 'Devicehub', 'Devicehub Documentation',
|
||||
author, 'Devicehub', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# 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
|
||||
# -- Extension configuration -------------------------------------------------
|
||||
|
||||
# -- Options for intersphinx extension ---------------------------------------
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'https://docs.python.org/': None}
|
||||
|
||||
autodoc_default_flags = ['members', 'private-members']
|
||||
autodoc_member_order = 'bysource'
|
||||
# -- Options for todo extension ----------------------------------------------
|
||||
|
||||
# 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
|
||||
|
||||
.. image:: https://www.ereuse.org/files/2017/04/DeviceHub-logo-V2.svg
|
||||
:height: 100px
|
||||
: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::
|
||||
:maxdepth: 4
|
||||
|
||||
snapshot
|
||||
|
||||
events
|
||||
tags
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
|
||||
.. image::
|
||||
* :ref:`search`
|
||||
|
|
|
@ -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 ereuse_utils.test import JSON
|
||||
from flask import Response
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from ereuse_devicehub.resources.models import Thing
|
||||
from ereuse_utils.test import JSON
|
||||
from teal.client import Client as TealClient
|
||||
from teal.marshmallow import ValidationError
|
||||
|
||||
|
@ -21,7 +21,7 @@ class Client(TealClient):
|
|||
|
||||
def open(self,
|
||||
uri: str,
|
||||
res: str or Type[Thing] = None,
|
||||
res: Union[str, Type[Thing]] = None,
|
||||
status: Union[int, Type[HTTPException], Type[ValidationError]] = 200,
|
||||
query: Iterable[Tuple[str, Any]] = tuple(),
|
||||
accept=JSON,
|
||||
|
@ -29,7 +29,7 @@ class Client(TealClient):
|
|||
item=None,
|
||||
headers: dict = None,
|
||||
token: str = None,
|
||||
**kw) -> (dict or str, Response):
|
||||
**kw) -> Tuple[Union[Dict[str, Any], str], Response]:
|
||||
if issubclass(res, Thing):
|
||||
res = res.__name__
|
||||
return super().open(uri, res, status, query, accept, content_type, item, headers, token,
|
||||
|
@ -44,7 +44,7 @@ class Client(TealClient):
|
|||
accept: str = JSON,
|
||||
headers: dict = 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)
|
||||
|
||||
def post(self,
|
||||
|
@ -57,7 +57,7 @@ class Client(TealClient):
|
|||
accept: str = JSON,
|
||||
headers: dict = 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,
|
||||
**kw)
|
||||
|
||||
|
@ -66,6 +66,20 @@ class Client(TealClient):
|
|||
assert isinstance(password, str)
|
||||
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):
|
||||
"""
|
||||
|
@ -87,7 +101,7 @@ class UserClient(Client):
|
|||
|
||||
def open(self,
|
||||
uri: str,
|
||||
res: str = None,
|
||||
res: Union[str, Type[Thing]] = None,
|
||||
status: int or HTTPException = 200,
|
||||
query: Iterable[Tuple[str, Any]] = tuple(),
|
||||
accept=JSON,
|
||||
|
@ -95,6 +109,6 @@ class UserClient(Client):
|
|||
item=None,
|
||||
headers: dict = 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,
|
||||
self.user['token'] if self.user else token, **kw)
|
||||
|
|
|
@ -1,27 +1,34 @@
|
|||
from distutils.version import StrictVersion
|
||||
from typing import Set
|
||||
|
||||
from ereuse_devicehub.resources.device import ComponentDef, ComputerDef, DesktopDef, DeviceDef, \
|
||||
GraphicCardDef, HardDriveDef, LaptopDef, MicrotowerDef, MotherboardDef, NetbookDef, \
|
||||
NetworkAdapterDef, ProcessorDef, RamModuleDef, ServerDef
|
||||
from ereuse_devicehub.resources.event import AddDef, EventDef, RemoveDef, SnapshotDef, TestDef, \
|
||||
TestHardDriveDef
|
||||
from ereuse_devicehub.resources.device import ComponentDef, ComputerDef, DataStorageDef, \
|
||||
DesktopDef, DeviceDef, GraphicCardDef, HardDriveDef, LaptopDef, MicrotowerDef, \
|
||||
MotherboardDef, NetbookDef, NetworkAdapterDef, ProcessorDef, RamModuleDef, ServerDef, \
|
||||
SolidStateDriveDef
|
||||
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.user import OrganizationDef, UserDef
|
||||
from teal.config import Config
|
||||
|
||||
|
||||
class DevicehubConfig(Config):
|
||||
RESOURCE_DEFINITIONS = (
|
||||
RESOURCE_DEFINITIONS = {
|
||||
DeviceDef, ComputerDef, DesktopDef, LaptopDef, NetbookDef, ServerDef,
|
||||
MicrotowerDef, ComponentDef, GraphicCardDef, HardDriveDef, MotherboardDef,
|
||||
NetworkAdapterDef, RamModuleDef, ProcessorDef, UserDef, OrganizationDef, TagDef, EventDef,
|
||||
AddDef, RemoveDef, SnapshotDef, TestDef, TestHardDriveDef
|
||||
)
|
||||
PASSWORD_SCHEMES = {'pbkdf2_sha256'}
|
||||
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/dh-db1'
|
||||
MIN_WORKBENCH = StrictVersion('11.0')
|
||||
MicrotowerDef, ComponentDef, GraphicCardDef, DataStorageDef, SolidStateDriveDef,
|
||||
HardDriveDef, MotherboardDef, NetworkAdapterDef, RamModuleDef, ProcessorDef, UserDef,
|
||||
OrganizationDef, TagDef, EventDef, AddDef, RemoveDef, EraseBasicDef, EraseSectorsDef,
|
||||
StepDef, StepZeroDef, StepRandomDef, RateDef, AggregateRateDef, WorkbenchRateDef,
|
||||
PhotoboxUserDef, PhotoboxSystemRateDef, InstallDef, SnapshotDef, TestDef,
|
||||
TestDataStorageDef, WorkbenchRateDef
|
||||
}
|
||||
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.
|
||||
"""
|
||||
ORGANIZATION_NAME = None # type: str
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from ereuse_devicehub.resources.device.schemas import Component, Computer, Desktop, Device, \
|
||||
GraphicCard, HardDrive, Laptop, Microtower, Motherboard, Netbook, NetworkAdapter, Processor, \
|
||||
RamModule, Server
|
||||
from ereuse_devicehub.resources.device.schemas import Component, Computer, DataStorage, Desktop, \
|
||||
Device, GraphicCard, HardDrive, Laptop, Microtower, Motherboard, Netbook, NetworkAdapter, \
|
||||
Processor, RamModule, Server, SolidStateDrive
|
||||
from ereuse_devicehub.resources.device.views import DeviceView
|
||||
from teal.resource import Converters, Resource
|
||||
|
||||
|
@ -44,10 +44,18 @@ class GraphicCardDef(ComponentDef):
|
|||
SCHEMA = GraphicCard
|
||||
|
||||
|
||||
class HardDriveDef(ComponentDef):
|
||||
class DataStorageDef(ComponentDef):
|
||||
SCHEMA = DataStorage
|
||||
|
||||
|
||||
class HardDriveDef(DataStorageDef):
|
||||
SCHEMA = HardDrive
|
||||
|
||||
|
||||
class SolidStateDriveDef(DataStorageDef):
|
||||
SCHEMA = SolidStateDrive
|
||||
|
||||
|
||||
class MotherboardDef(ComponentDef):
|
||||
SCHEMA = Motherboard
|
||||
|
||||
|
|
|
@ -3,37 +3,38 @@ from itertools import chain
|
|||
from operator import attrgetter
|
||||
from typing import Dict, Set
|
||||
|
||||
from ereuse_utils.naming import Naming
|
||||
from sqlalchemy import BigInteger, Column, Float, ForeignKey, Integer, Sequence, SmallInteger, \
|
||||
Unicode, inspect
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from sqlalchemy.orm import ColumnProperty, backref, relationship
|
||||
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_utils.naming import Naming
|
||||
from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, check_range
|
||||
|
||||
|
||||
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)
|
||||
hid = Column(Unicode(STR_BIG_SIZE), unique=True) # type: str
|
||||
pid = Column(Unicode(STR_SIZE)) # type: str
|
||||
gid = Column(Unicode(STR_SIZE)) # type: str
|
||||
model = Column(Unicode(STR_BIG_SIZE)) # type: str
|
||||
manufacturer = Column(Unicode(STR_SIZE)) # type: str
|
||||
serial_number = Column(Unicode(STR_SIZE)) # type: str
|
||||
weight = Column(Float(precision=3, decimal_return_scale=3),
|
||||
check_range('weight', 0.1, 3)) # type: float
|
||||
width = Column(Float(precision=3, decimal_return_scale=3),
|
||||
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
|
||||
hid = Column(Unicode(STR_BIG_SIZE), unique=True)
|
||||
model = Column(Unicode(STR_BIG_SIZE))
|
||||
manufacturer = Column(Unicode(STR_SIZE))
|
||||
serial_number = Column(Unicode(STR_SIZE))
|
||||
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 3))
|
||||
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 3))
|
||||
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 3))
|
||||
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 3))
|
||||
color = Column(ColorType)
|
||||
|
||||
@property
|
||||
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:
|
||||
super().__init__(*args, **kw)
|
||||
|
@ -79,7 +80,7 @@ class Device(Thing):
|
|||
|
||||
|
||||
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):
|
||||
|
@ -103,7 +104,7 @@ class Microtower(Computer):
|
|||
|
||||
|
||||
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 = relationship(Computer,
|
||||
|
@ -145,23 +146,31 @@ class JoinedComponentTableMixin:
|
|||
|
||||
|
||||
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):
|
||||
size = Column(Integer, check_range('size', min=1, max=10 ** 8)) # type: int
|
||||
class DataStorage(JoinedComponentTableMixin, Component):
|
||||
size = Column(Integer, check_range('size', min=1, max=10 ** 8))
|
||||
|
||||
|
||||
class HardDrive(DataStorage):
|
||||
pass
|
||||
|
||||
|
||||
class SolidStateDrive(DataStorage):
|
||||
pass
|
||||
|
||||
|
||||
class Motherboard(JoinedComponentTableMixin, Component):
|
||||
slots = Column(SmallInteger, check_range('slots')) # type: int
|
||||
usb = Column(SmallInteger, check_range('usb')) # type: int
|
||||
firewire = Column(SmallInteger, check_range('firewire')) # type: int
|
||||
serial = Column(SmallInteger, check_range('serial')) # type: int
|
||||
pcmcia = Column(SmallInteger, check_range('pcmcia')) # type: int
|
||||
slots = Column(SmallInteger, check_range('slots'))
|
||||
usb = Column(SmallInteger, check_range('usb'))
|
||||
firewire = Column(SmallInteger, check_range('firewire'))
|
||||
serial = Column(SmallInteger, check_range('serial'))
|
||||
pcmcia = Column(SmallInteger, check_range('pcmcia'))
|
||||
|
||||
|
||||
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):
|
||||
|
|
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 '
|
||||
'use to ID a device globally.')
|
||||
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))
|
||||
manufacturer = Str(validate=Length(max=STR_SIZE))
|
||||
serial_number = Str(data_key='serialNumber')
|
||||
|
@ -70,7 +65,7 @@ class GraphicCard(Component):
|
|||
description='The amount of memory of the Graphic Card in MB.')
|
||||
|
||||
|
||||
class HardDrive(Component):
|
||||
class DataStorage(Component):
|
||||
size = Integer(validate=Range(0, 10 ** 8),
|
||||
unit=UnitCodes.mbyte,
|
||||
description='The size of the hard-drive in MB.')
|
||||
|
@ -79,6 +74,14 @@ class HardDrive(Component):
|
|||
benchmarks = NestedOn('BenchmarkHardDrive', load_only=True, many=True)
|
||||
|
||||
|
||||
class HardDrive(DataStorage):
|
||||
pass
|
||||
|
||||
|
||||
class SolidStateDrive(DataStorage):
|
||||
pass
|
||||
|
||||
|
||||
class Motherboard(Component):
|
||||
slots = Integer(validate=Range(1, 20), description='PCI slots the motherboard has.')
|
||||
usb = Integer(validate=Range(0, 20))
|
||||
|
|
|
@ -47,13 +47,14 @@ class Sync:
|
|||
:return: A tuple of:
|
||||
|
||||
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.
|
||||
2. A list of Add / Remove (not yet added to session).
|
||||
"""
|
||||
db_device = self.execute_register(device)
|
||||
db_components, events = OrderedSet(), OrderedSet()
|
||||
if components is not None: # We have component info (see above)
|
||||
assert isinstance(db_device, Computer)
|
||||
blacklist = set() # type: Set[int]
|
||||
not_new_components = set()
|
||||
for component in components:
|
||||
|
@ -122,7 +123,7 @@ class Sync:
|
|||
This method tries to get an existing device using the HID
|
||||
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
|
||||
from the database. In this case we do not
|
||||
"touch" any of its values on the DB.
|
||||
|
@ -187,7 +188,7 @@ class Sync:
|
|||
setattr(db_device, field_name, value)
|
||||
|
||||
@staticmethod
|
||||
def add_remove(device: Device,
|
||||
def add_remove(device: Computer,
|
||||
components: Set[Component]) -> OrderedSet:
|
||||
"""
|
||||
Generates the Add and Remove events (but doesn't add them to
|
||||
|
@ -209,7 +210,7 @@ class Sync:
|
|||
adding = components - old_components
|
||||
if adding:
|
||||
# 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
|
||||
|
||||
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 ereuse_devicehub.resources.device.sync import Sync
|
||||
from ereuse_devicehub.resources.event.schemas import Add, Event, Remove, Snapshot, Test, \
|
||||
TestHardDrive
|
||||
from ereuse_devicehub.resources.event.schemas import Add, AggregateRate, EraseBasic, Event, \
|
||||
Install, PhotoboxSystemRate, PhotoboxUserRate, Rate, Remove, Snapshot, Step, StepRandom, \
|
||||
StepZero, Test, TestDataStorage, WorkbenchRate, EraseSectors
|
||||
from ereuse_devicehub.resources.event.views import EventView, SnapshotView
|
||||
from teal.resource import Converters, Resource
|
||||
|
||||
|
@ -22,6 +23,50 @@ class RemoveDef(EventDef):
|
|||
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):
|
||||
SCHEMA = Snapshot
|
||||
VIEW = SnapshotView
|
||||
|
@ -38,5 +83,5 @@ class TestDef(EventDef):
|
|||
SCHEMA = Test
|
||||
|
||||
|
||||
class TestHardDriveDef(TestDef):
|
||||
SCHEMA = TestHardDrive
|
||||
class TestDataStorageDef(TestDef):
|
||||
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 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.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_utils import ColorType
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.device.models import Component, Device
|
||||
from ereuse_devicehub.resources.event.enums import Appearance, Bios, Functionality, Orientation, \
|
||||
SoftwareType, StepTypes, TestHardDriveLength
|
||||
from ereuse_devicehub.resources.device.models import Component, DataStorage, Device
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, BOX_RATE_3, BOX_RATE_5, Bios, \
|
||||
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.user.models import User
|
||||
from teal.db import CASCADE, CASCADE_OWN, INHERIT_COND, POLYMORPHIC_ID, POLYMORPHIC_ON, \
|
||||
StrictVersionType, check_range
|
||||
from teal.db import ArrayOfEnum, CASCADE, CASCADE_OWN, INHERIT_COND, POLYMORPHIC_ID, \
|
||||
POLYMORPHIC_ON, StrictVersionType, check_range
|
||||
|
||||
|
||||
class JoinedTableMixin:
|
||||
# noinspection PyMethodParameters
|
||||
@declared_attr
|
||||
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):
|
||||
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)
|
||||
date = Column(DateTime)
|
||||
secured = Column(Boolean, default=False, nullable=False)
|
||||
type = Column(Unicode)
|
||||
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)
|
||||
date = Column(DateTime)
|
||||
|
||||
snapshot_id = Column(BigInteger, ForeignKey('snapshot.id',
|
||||
snapshot_id = Column(UUID(as_uuid=True), ForeignKey('snapshot.id',
|
||||
use_alter=True,
|
||||
name='snapshot_events'))
|
||||
snapshot = relationship('Snapshot',
|
||||
backref=backref('events',
|
||||
lazy=True,
|
||||
cascade=CASCADE,
|
||||
collection_class=OrderedSet),
|
||||
cascade=CASCADE_OWN,
|
||||
collection_class=set),
|
||||
primaryjoin='Event.snapshot_id == Snapshot.id')
|
||||
|
||||
author_id = Column(UUID(as_uuid=True),
|
||||
|
@ -55,12 +62,23 @@ class Event(Thing):
|
|||
components = relationship(Component,
|
||||
backref=backref('events_components',
|
||||
lazy=True,
|
||||
order_by=lambda: Event.id,
|
||||
order_by=lambda: Event.created,
|
||||
collection_class=OrderedSet),
|
||||
secondary=lambda: EventComponent.__table__,
|
||||
order_by=lambda: Device.id,
|
||||
order_by=lambda: Component.id,
|
||||
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
|
||||
def __mapper_args__(cls):
|
||||
"""
|
||||
|
@ -79,8 +97,8 @@ class Event(Thing):
|
|||
|
||||
|
||||
class EventComponent(db.Model):
|
||||
device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||
event_id = Column(BigInteger, ForeignKey(Event.id), primary_key=True)
|
||||
device_id = Column(BigInteger, ForeignKey(Component.id), primary_key=True)
|
||||
event_id = Column(UUID(as_uuid=True), ForeignKey(Event.id), primary_key=True)
|
||||
|
||||
|
||||
class EventWithOneDevice(Event):
|
||||
|
@ -89,22 +107,19 @@ class EventWithOneDevice(Event):
|
|||
backref=backref('events_one',
|
||||
lazy=True,
|
||||
cascade=CASCADE,
|
||||
order_by=lambda: EventWithOneDevice.id,
|
||||
order_by=lambda: EventWithOneDevice.created,
|
||||
collection_class=OrderedSet),
|
||||
primaryjoin=Device.id == device_id)
|
||||
|
||||
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):
|
||||
"""
|
||||
Note that these events are not deleted when a device is deleted.
|
||||
"""
|
||||
devices = relationship(Device,
|
||||
backref=backref('events_multiple',
|
||||
lazy=True,
|
||||
order_by=lambda: EventWithMultipleDevices.id,
|
||||
order_by=lambda: EventWithMultipleDevices.created,
|
||||
collection_class=OrderedSet),
|
||||
secondary=lambda: EventDevice.__table__,
|
||||
order_by=lambda: Device.id)
|
||||
|
@ -115,7 +130,8 @@ class EventWithMultipleDevices(Event):
|
|||
|
||||
class EventDevice(db.Model):
|
||||
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):
|
||||
|
@ -139,11 +155,11 @@ class Deallocate(JoinedTableMixin, EventWithMultipleDevices):
|
|||
|
||||
|
||||
class EraseBasic(JoinedTableMixin, EventWithOneDevice):
|
||||
starting_time = Column(DateTime, nullable=False)
|
||||
ending_time = Column(DateTime, nullable=False)
|
||||
secure_random_steps = Column(SmallInteger, check_range('secure_random_steps', min=0),
|
||||
start_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),
|
||||
nullable=False)
|
||||
success = Column(Boolean, nullable=False)
|
||||
clean_with_zeros = Column(Boolean, nullable=False)
|
||||
|
||||
|
||||
|
@ -152,54 +168,63 @@ class EraseSectors(EraseBasic):
|
|||
|
||||
|
||||
class Step(db.Model):
|
||||
id = Column(BigInteger, Sequence('step_seq'), primary_key=True)
|
||||
num = Column(SmallInteger, nullable=False)
|
||||
type = Column(DBEnum(StepTypes), nullable=False)
|
||||
success = Column(Boolean, nullable=False)
|
||||
starting_time = Column(DateTime, nullable=False)
|
||||
ending_time = Column(DateTime, CheckConstraint('ending_time > starting_time'), nullable=False)
|
||||
secure_random_steps = Column(SmallInteger, check_range('secure_random_steps', min=0),
|
||||
erasure_id = Column(UUID(as_uuid=True), ForeignKey(EraseBasic.id), primary_key=True)
|
||||
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
||||
num = Column(SmallInteger, primary_key=True)
|
||||
error = Column(Boolean, default=False, nullable=False)
|
||||
start_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),
|
||||
nullable=False)
|
||||
clean_with_zeros = Column(Boolean, nullable=False)
|
||||
|
||||
erasure_id = Column(BigInteger, ForeignKey(EraseBasic.id))
|
||||
erasure = relationship(EraseBasic, backref=backref('steps', cascade=CASCADE_OWN))
|
||||
erasure = relationship(EraseBasic,
|
||||
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):
|
||||
uuid = Column(UUID(as_uuid=True), nullable=False, unique=True) # type: UUID
|
||||
version = Column(StrictVersionType(STR_SM_SIZE), nullable=False) # type: str
|
||||
software = Column(DBEnum(SoftwareType), nullable=False) # type: SoftwareType
|
||||
appearance = Column(DBEnum(Appearance)) # type: Appearance
|
||||
appearance_score = Column(SmallInteger,
|
||||
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
|
||||
uuid = Column(UUID(as_uuid=True), nullable=False, unique=True)
|
||||
version = Column(StrictVersionType(STR_SM_SIZE), nullable=False)
|
||||
software = Column(DBEnum(SnapshotSoftware), nullable=False)
|
||||
elapsed = Column(Interval, nullable=False)
|
||||
expected_events = Column(ArrayOfEnum(DBEnum(SnapshotExpectedEvents)))
|
||||
|
||||
@validates('components')
|
||||
def validate_components_only_workbench(self, _, components):
|
||||
if self.software != SoftwareType.Workbench:
|
||||
if components:
|
||||
raise ValueError('Only Snapshots from Workbench can have components.')
|
||||
return components
|
||||
|
||||
class Install(JoinedTableMixin, EventWithOneDevice):
|
||||
name = Column(Unicode(STR_BIG_SIZE), nullable=False)
|
||||
elapsed = Column(Interval, nullable=False)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
snapshot = relationship(Snapshot,
|
||||
backref=backref('request',
|
||||
lazy=True,
|
||||
|
@ -207,25 +232,132 @@ class SnapshotRequest(db.Model):
|
|||
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):
|
||||
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):
|
||||
id = Column(BigInteger, ForeignKey(Test.id), primary_key=True)
|
||||
class TestDataStorage(Test):
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Test.id), primary_key=True)
|
||||
length = Column(DBEnum(TestHardDriveLength), nullable=False) # todo from type
|
||||
status = Column(Unicode(STR_SIZE), nullable=False)
|
||||
lifetime = Column(Interval, nullable=False)
|
||||
first_error = Column(Integer)
|
||||
# todo error becomes Test.success
|
||||
first_error = Column(SmallInteger, nullable=False, default=0)
|
||||
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):
|
||||
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 marshmallow import ValidationError, post_load, validates_schema
|
||||
from marshmallow.fields import Boolean, DateTime, Integer, Nested, String, TimeDelta, UUID
|
||||
from marshmallow import ValidationError, validates_schema
|
||||
from marshmallow.fields import Boolean, DateTime, Float, Integer, Nested, String, TimeDelta, UUID
|
||||
from marshmallow.validate import Length, Range
|
||||
from marshmallow_enum import EnumField
|
||||
|
||||
from ereuse_devicehub.marshmallow import NestedOn
|
||||
from ereuse_devicehub.resources.device.schemas import Component, Device
|
||||
from ereuse_devicehub.resources.event.enums import Appearance, Bios, Functionality, Orientation, \
|
||||
SoftwareType, StepTypes, TestHardDriveLength
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
|
||||
RATE_POSITIVE, RatingSoftware, SnapshotExpectedEvents, SnapshotSoftware, TestHardDriveLength
|
||||
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
||||
from ereuse_devicehub.resources.schemas import Thing
|
||||
from ereuse_devicehub.resources.user.schemas import User
|
||||
from teal.marshmallow import Color, Version
|
||||
from teal.marshmallow import Version
|
||||
from teal.resource import Schema
|
||||
|
||||
|
||||
|
@ -23,16 +23,12 @@ class Event(Thing):
|
|||
date = DateTime('iso', description='When this event happened. '
|
||||
'Leave it blank if it is happening now. '
|
||||
'This is used when creating events retroactively.')
|
||||
secured = Boolean(default=False,
|
||||
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.')
|
||||
error = Boolean(default=False, description='Did the event fail?')
|
||||
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)
|
||||
description = String(default='', description='A comment about the event.')
|
||||
components = NestedOn(Component, dump_only=True, many=True)
|
||||
description = String(default='', description='A comment about the event.')
|
||||
|
||||
|
||||
class EventWithOneDevice(Event):
|
||||
|
@ -67,12 +63,12 @@ class Deallocate(EventWithMultipleDevices):
|
|||
|
||||
|
||||
class EraseBasic(EventWithOneDevice):
|
||||
starting_time = DateTime(required=True, data_key='startingTime')
|
||||
ending_time = DateTime(required=True, data_key='endingTime')
|
||||
start_time = DateTime(required=True, data_key='startTime')
|
||||
end_time = DateTime(required=True, data_key='endTime')
|
||||
secure_random_steps = Integer(validate=Range(min=0), required=True,
|
||||
data_key='secureRandomSteps')
|
||||
success = Boolean(required=True)
|
||||
clean_with_zeros = Boolean(required=True, data_key='cleanWithZeros')
|
||||
steps = NestedOn('Step', many=True, required=True)
|
||||
|
||||
|
||||
class EraseSectors(EraseBasic):
|
||||
|
@ -81,46 +77,94 @@ class EraseSectors(EraseBasic):
|
|||
|
||||
class Step(Schema):
|
||||
id = Integer(dump_only=True)
|
||||
type = EnumField(StepTypes, required=True)
|
||||
starting_time = DateTime(required=True, data_key='startingTime')
|
||||
ending_time = DateTime(required=True, data_key='endingTime')
|
||||
type = String(description='Only required when it is nested.')
|
||||
start_time = DateTime(required=True, data_key='startTime')
|
||||
end_time = DateTime(required=True, data_key='endTime')
|
||||
secure_random_steps = Integer(validate=Range(min=0),
|
||||
required=True,
|
||||
data_key='secureRandomSteps')
|
||||
success = Boolean(required=True)
|
||||
clean_with_zeros = Boolean(required=True, data_key='cleanWithZeros')
|
||||
error = Boolean(default=False, description='Did the event fail?')
|
||||
|
||||
|
||||
class Condition(Schema):
|
||||
appearance = EnumField(Appearance,
|
||||
required=True,
|
||||
description='Grades the imperfections that aesthetically '
|
||||
'affect the device, but not its usage.')
|
||||
appearance_score = Integer(validate=Range(-3, 5), dump_only=True)
|
||||
functionality = EnumField(Functionality,
|
||||
required=True,
|
||||
description='Grades the defects of a device that affect its usage.')
|
||||
functionality_score = Integer(validate=Range(-3, 5),
|
||||
class StepZero(Step):
|
||||
pass
|
||||
|
||||
|
||||
class StepRandom(Step):
|
||||
pass
|
||||
|
||||
|
||||
class Rate(EventWithOneDevice):
|
||||
rating = Integer(validate=Range(*RATE_POSITIVE),
|
||||
dump_only=True,
|
||||
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.')
|
||||
bios = EnumField(Bios, description='How difficult it has been to set the bios to '
|
||||
'boot from the network.')
|
||||
general = Integer(dump_only=True,
|
||||
validate=Range(0, 5),
|
||||
description='The grade of the device.')
|
||||
appearance_range = EnumField(AppearanceRange,
|
||||
required=True,
|
||||
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),
|
||||
required=True,
|
||||
description='The name of the OS installed.')
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
success = Boolean(required=True)
|
||||
|
||||
|
||||
class Inventory(Schema):
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
|
||||
|
||||
class Snapshot(EventWithOneDevice):
|
||||
|
@ -130,57 +174,50 @@ class Snapshot(EventWithOneDevice):
|
|||
|
||||
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,
|
||||
many=True,
|
||||
description='A list of components that are inside of the device'
|
||||
'at the moment of this Snapshot.'
|
||||
'Order is preserved, so the component num 0 when'
|
||||
'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
|
||||
def validate_workbench_version(self, data: dict):
|
||||
if data['software'] == SoftwareType.Workbench:
|
||||
if data['software'] == SnapshotSoftware.Workbench:
|
||||
if data['version'] < app.config['MIN_WORKBENCH']:
|
||||
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']
|
||||
)
|
||||
|
||||
@validates_schema
|
||||
def validate_components_only_workbench(self, data: dict):
|
||||
if data['software'] != SoftwareType.Workbench:
|
||||
if data['software'] != SnapshotSoftware.Workbench:
|
||||
if data['components'] is not None:
|
||||
raise ValidationError('Only Workbench can add component info',
|
||||
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):
|
||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||
success = Boolean(required=True)
|
||||
|
||||
|
||||
class TestHardDrive(Test):
|
||||
class TestDataStorage(Test):
|
||||
length = EnumField(TestHardDriveLength, required=True)
|
||||
status = String(validate=Length(max=STR_SIZE), required=True)
|
||||
lifetime = TimeDelta(precision=TimeDelta.DAYS, required=True)
|
||||
|
|
|
@ -4,9 +4,9 @@ from flask import request
|
|||
from sqlalchemy.util import OrderedSet
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
from ereuse_devicehub.resources.event.enums import SoftwareType
|
||||
from ereuse_devicehub.resources.event.models import Event, Snapshot, TestHardDrive
|
||||
from ereuse_devicehub.resources.device.models import Computer
|
||||
from ereuse_devicehub.resources.enums import SnapshotSoftware
|
||||
from ereuse_devicehub.resources.event.models import Event, Snapshot, TestDataStorage
|
||||
from teal.resource import View
|
||||
|
||||
|
||||
|
@ -31,12 +31,16 @@ class SnapshotView(View):
|
|||
# Note that if we set the device / components into the snapshot
|
||||
# model object, when we flush them to the db we will flush
|
||||
# snapshot, and we want to wait to flush snapshot at the end
|
||||
device = s.pop('device') # type: Device
|
||||
components = s.pop('components') if s['software'] == SoftwareType.Workbench else None
|
||||
device = s.pop('device') # type: Computer
|
||||
components = s.pop('components') if s['software'] == SnapshotSoftware.Workbench else None
|
||||
if 'events' in s:
|
||||
events = s.pop('events')
|
||||
# todo perform events
|
||||
# noinspection PyArgumentList
|
||||
snapshot = Snapshot(**s)
|
||||
snapshot.device, snapshot.events = self.resource_def.sync.run(device, components)
|
||||
snapshot.components = snapshot.device.components
|
||||
# todo compute rating
|
||||
# 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
|
||||
ordered_components = OrderedSet(x for x in snapshot.components)
|
||||
|
@ -57,7 +61,7 @@ class TestHardDriveView(View):
|
|||
def post(self):
|
||||
t = request.get_json() # type: dict
|
||||
# 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
|
||||
|
||||
|
||||
|
|
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_BIG_SIZE = 128
|
||||
STR_SM_SIZE = 32
|
||||
STR_XSM_SIZE = 16
|
||||
|
||||
|
||||
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
|
||||
# will be set as persistent and added to session
|
||||
# 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,
|
||||
backref=backref('tags', lazy=True),
|
||||
primaryjoin=Organization.id == org_id,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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.dialects.postgresql import UUID
|
||||
from sqlalchemy_utils import CountryType, EmailType, PasswordType
|
||||
|
@ -42,9 +42,12 @@ class Organization(Thing):
|
|||
)
|
||||
|
||||
@classmethod
|
||||
def get_default_org(cls) -> 'Organization':
|
||||
def get_default_org_id(cls) -> UUID:
|
||||
"""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:
|
||||
return '<Org {0.id}: {0.name}>'.format(self)
|
||||
|
|
|
@ -3,7 +3,6 @@ device:
|
|||
serialNumber: 'p1'
|
||||
model: 'p1'
|
||||
type: 'Desktop'
|
||||
secured: False
|
||||
components:
|
||||
- manufacturer: 'p1c1m'
|
||||
serialNumber: 'p1c1s'
|
||||
|
@ -18,9 +17,6 @@ components:
|
|||
serialNumber: 'p1c3s'
|
||||
type: 'GraphicCard'
|
||||
memory: 1.5
|
||||
condition:
|
||||
appearance: 'A'
|
||||
functionality: 'B'
|
||||
elapsed: 25
|
||||
software: 'Workbench'
|
||||
uuid: '76860eca-c3fd-41f6-a801-6af7bd8cf832'
|
||||
|
|
|
@ -3,7 +3,6 @@ device:
|
|||
serialNumber: 'p2s'
|
||||
model: 'p2'
|
||||
type: 'Microtower'
|
||||
secured: False
|
||||
components:
|
||||
- manufacturer: 'p2c1m'
|
||||
serialNumber: 'p2c1s'
|
||||
|
@ -14,9 +13,6 @@ components:
|
|||
speed: 1.23
|
||||
cores: 2
|
||||
type: 'Processor'
|
||||
condition:
|
||||
appearance: 'A'
|
||||
functionality: 'B'
|
||||
elapsed: 25
|
||||
software: 'Workbench'
|
||||
uuid: 'f2e02261-87a1-4a50-b9b7-92c0e476e5f2'
|
||||
|
|
|
@ -3,7 +3,6 @@ device:
|
|||
serialNumber: 'p1'
|
||||
model: 'p1'
|
||||
type: 'Desktop'
|
||||
secured: False
|
||||
components:
|
||||
- manufacturer: 'p1c2m'
|
||||
serialNumber: 'p1c2s'
|
||||
|
@ -15,9 +14,6 @@ components:
|
|||
serialNumber: 'p1c3s'
|
||||
type: 'GraphicCard'
|
||||
memory: 1.5
|
||||
condition:
|
||||
appearance: 'C'
|
||||
functionality: 'C'
|
||||
elapsed: 30
|
||||
software: 'Workbench'
|
||||
uuid: '3be271b6-5ef4-47d8-8237-5e1133eebfc6'
|
||||
|
|
|
@ -3,7 +3,6 @@ device:
|
|||
serialNumber: 'p1'
|
||||
model: 'p1'
|
||||
type: 'Desktop'
|
||||
secured: False
|
||||
components:
|
||||
- manufacturer: 'p1c4m'
|
||||
serialNumber: 'p1c4s'
|
||||
|
@ -13,9 +12,6 @@ components:
|
|||
serialNumber: 'p1c3s'
|
||||
type: 'GraphicCard'
|
||||
memory: 1.5
|
||||
condition:
|
||||
appearance: 'A'
|
||||
functionality: 'A'
|
||||
elapsed: 25
|
||||
software: 'Workbench'
|
||||
uuid: 'fd007eb4-48e3-454a-8763-169491904c6e'
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
uuid: 'f5efd26e-8754-46bc-87bf-fbccc39d60d9'
|
||||
type: 'Snapshot'
|
||||
uuid: 'f5efd26e-8754-46bc-87bf-fbccc39d60d9'
|
||||
version: '11.0'
|
||||
software: 'Workbench'
|
||||
condition:
|
||||
appearance: 'A'
|
||||
functionality: 'B'
|
||||
events:
|
||||
- type: 'WorkbenchRate'
|
||||
appearanceRange: 'A'
|
||||
functionalityRange: 'B'
|
||||
labelling: True
|
||||
bios: 'B'
|
||||
elapsed: 4
|
||||
|
|
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
|
||||
|
||||
import pytest
|
||||
from colour import Color
|
||||
|
||||
from ereuse_utils.naming import Naming
|
||||
from pytest import raises
|
||||
from sqlalchemy.util import OrderedSet
|
||||
|
@ -79,15 +81,13 @@ def test_physical_properties():
|
|||
model='ml',
|
||||
manufacturer='mr',
|
||||
width=2.0,
|
||||
pid='abc')
|
||||
color=Color())
|
||||
pc = Computer()
|
||||
pc.components.add(c)
|
||||
db.session.add(pc)
|
||||
db.session.commit()
|
||||
assert c.physical_properties == {
|
||||
'gid': None,
|
||||
'usb': 3,
|
||||
'pid': 'abc',
|
||||
'serial_number': 'sn',
|
||||
'pcmcia': None,
|
||||
'model': 'ml',
|
||||
|
@ -97,7 +97,9 @@ def test_physical_properties():
|
|||
'manufacturer': 'mr',
|
||||
'weight': 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(Test(device=pc,
|
||||
elapsed=timedelta(seconds=4),
|
||||
success=True,
|
||||
error=False,
|
||||
author=User(email='bar@bar.com')))
|
||||
db.session.commit()
|
||||
pc, _ = user.get(res=Device, item=1)
|
||||
assert len(pc['events']) == 1
|
||||
assert pc['events'][0]['type'] == 'Test'
|
||||
assert pc['events'][0]['id'] == 1
|
||||
assert pc['events'][0]['device'] == 1
|
||||
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 'events_components' not in pc, 'events_components 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
|
||||
from flask import g
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
from ereuse_devicehub.resources.event.models import EventWithOneDevice
|
||||
from ereuse_devicehub.resources.device.models import Device, GraphicCard, HardDrive, \
|
||||
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
|
||||
|
||||
|
||||
|
@ -22,3 +27,101 @@ def test_author():
|
|||
assert e.author_id is None
|
||||
db.session.commit()
|
||||
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
|
||||
|
||||
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,
|
||||
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 typing import List
|
||||
from distutils.version import StrictVersion
|
||||
from typing import List, Tuple
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
@ -8,10 +9,11 @@ from ereuse_devicehub.client import UserClient
|
|||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.devicehub import Devicehub
|
||||
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.event.models import Appearance, Bios, Event, Functionality, \
|
||||
Snapshot, SnapshotRequest, SoftwareType
|
||||
from ereuse_devicehub.resources.enums import Bios, RatingSoftware, SnapshotSoftware
|
||||
from ereuse_devicehub.resources.event.models import EraseBasic, Event, Snapshot, SnapshotRequest, \
|
||||
WorkbenchRate
|
||||
from ereuse_devicehub.resources.tag import Tag
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
from tests.conftest import file
|
||||
|
@ -19,7 +21,8 @@ from tests.conftest import file
|
|||
|
||||
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(device2, dict) and device2
|
||||
|
@ -39,10 +42,21 @@ def assert_similar_components(components1: List[dict], components2: List[dict]):
|
|||
|
||||
def snapshot_and_check(user: UserClient,
|
||||
input_snapshot: dict,
|
||||
event_types: tuple or list = tuple(),
|
||||
event_types: Tuple[str] = tuple(),
|
||||
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)
|
||||
assert tuple(e['type'] for e in snapshot['events']) == event_types
|
||||
|
@ -76,22 +90,25 @@ def test_snapshot_model():
|
|||
snapshot = Snapshot(uuid=uuid4(),
|
||||
date=datetime.now(),
|
||||
version='1.0',
|
||||
software=SoftwareType.DesktopApp,
|
||||
appearance=Appearance.A,
|
||||
appearance_score=5,
|
||||
functionality=Functionality.A,
|
||||
functionality_score=5,
|
||||
labelling=False,
|
||||
bios=Bios.C,
|
||||
condition=5,
|
||||
software=SnapshotSoftware.DesktopApp,
|
||||
elapsed=timedelta(seconds=25))
|
||||
snapshot.device = device
|
||||
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.commit()
|
||||
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.commit()
|
||||
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']],
|
||||
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
|
||||
|
@ -255,7 +272,7 @@ def _test_snapshot_computer_no_hid(user: UserClient):
|
|||
|
||||
def test_snapshot_mismatch_id():
|
||||
"""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
|
||||
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
|
||||
pc2['device']['tags'] = [{'type': 'Tag', 'id': tag_id}] # Set tag from pc1 to pc2
|
||||
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