Create eTags, lots, and events in dummy

This commit is contained in:
Xavier Bustamante Talavera 2018-09-20 16:40:41 +02:00
parent 32837f5f59
commit f56ca473e8
9 changed files with 114 additions and 28 deletions

View File

@ -1,4 +1,7 @@
import itertools
import json
from pathlib import Path from pathlib import Path
from typing import Set
import click import click
import click_spinner import click_spinner
@ -7,22 +10,26 @@ import yaml
from ereuse_devicehub.client import UserClient from ereuse_devicehub.client import UserClient
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.agent.models import Person from ereuse_devicehub.resources.agent.models import Person
from ereuse_devicehub.resources.event.models import Snapshot from ereuse_devicehub.resources.event import models as m
from ereuse_devicehub.resources.inventory import Inventory from ereuse_devicehub.resources.inventory import Inventory
from ereuse_devicehub.resources.tag.model import Tag from ereuse_devicehub.resources.tag.model import Tag
from ereuse_devicehub.resources.user import User from ereuse_devicehub.resources.user import User
class Dummy: class Dummy:
SNAPSHOTS = (
'workbench-server-1',
'computer-monitor'
)
TAGS = ( TAGS = (
'tag1', 'tag1',
'tag2', 'tag2',
'tag3' 'tag3'
) )
"""Tags to create."""
ET = (
('A0000000000001', 'DT-AAAAA'),
('A0000000000002', 'DT-BBBBB'),
)
"""eTags to create."""
ORG = 'eReuse.org CAT', 'G-60437761', 'ES'
"""An organization to create."""
def __init__(self, app) -> None: def __init__(self, app) -> None:
super().__init__() super().__init__()
@ -34,20 +41,60 @@ class Dummy:
'Do you want to continue?') 'Do you want to continue?')
def run(self): def run(self):
print('Preparing the database...'.ljust(30), end='') print('Preparing the database...'.ljust(30), end='')
runner = self.app.test_cli_runner()
with click_spinner.spinner(): with click_spinner.spinner():
self.app.init_db(erase=True) self.app.init_db(erase=True)
out = runner.invoke(args=['create-org', *self.ORG], catch_exceptions=False).output
org_id = json.loads(out)['id']
user = self.user_client('user@dhub.com', '1234') user = self.user_client('user@dhub.com', '1234')
# todo put user's agent into Org
for id in self.TAGS: for id in self.TAGS:
user.post({'id': id}, res=Tag) user.post({'id': id}, res=Tag)
for id, sec in self.ET:
runner.invoke(args=[
'create-tag', id,
'-p', 'https://t.devicetag.io',
'-s', sec,
'-o', org_id
],
catch_exceptions=False)
files = tuple(Path(__file__).parent.joinpath('files').iterdir()) files = tuple(Path(__file__).parent.joinpath('files').iterdir())
print('done.') print('done.')
pcs = set() # type: Set[int]
with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar: with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar:
for path in bar: for path in bar:
with path.open() as f: with path.open() as f:
snapshot = yaml.load(f) snapshot = yaml.load(f)
user.post(res=Snapshot, data=snapshot) s, _ = user.post(res=m.Snapshot, data=snapshot)
pcs.add(s['device']['id'])
inventory, _ = user.get(res=Inventory) inventory, _ = user.get(res=Inventory)
assert len(inventory['devices']) assert len(inventory['devices'])
# Link tags and eTags
for tag, pc in zip((self.TAGS[1], self.TAGS[2], self.ET[0][0], self.ET[1][1]), pcs):
user.put({}, res=Tag, item='{}/device/{}'.format(tag, pc), status=204)
# Perform generic events
for pc, model in zip(pcs,
{m.ToRepair, m.Repair, m.ToPrepare, m.ReadyToUse, m.ToPrepare,
m.Prepare}):
user.post({'type': model.t, 'devices': [pc]}, res=m.Event)
# Perform a Sell to several devices
user.post(
{
'type': m.Sell.t,
'to': user.user['individuals'][0]['id'],
'devices': list(itertools.islice(pcs, len(pcs) // 2))
},
res=m.Event)
from tests.test_lot import test_post_add_children_view, test_post_add_device_view
test_post_add_children_view(user)
# todo this does not add devices to lots
test_post_add_device_view(user)
print('⭐ Done.') print('⭐ Done.')
def user_client(self, email: str, password: str): def user_client(self, email: str, password: str):

View File

@ -1,3 +1,5 @@
import json
import click import click
from flask import current_app as app from flask import current_app as app
from teal.db import SQLAlchemy from teal.db import SQLAlchemy
@ -39,7 +41,9 @@ class OrganizationDef(AgentDef):
)) ))
db.session.add(org) db.session.add(org)
db.session.commit() db.session.commit()
return self.schema.dump(org) o = self.schema.dump(org)
print(json.dumps(o, indent=2))
return o
def init_db(self, db: SQLAlchemy): def init_db(self, db: SQLAlchemy):
"""Creates the default organization.""" """Creates the default organization."""

View File

@ -43,11 +43,6 @@ class Agent(Thing):
telephone = Column(PhoneNumberType()) telephone = Column(PhoneNumberType())
email = Column(EmailType, unique=True) email = Column(EmailType, unique=True)
user_id = Column(UUID(as_uuid=True), ForeignKey(User.id), unique=True)
user = relationship(User,
backref=backref('individuals', lazy=True, collection_class=set),
primaryjoin=user_id == User.id)
__table_args__ = ( __table_args__ = (
UniqueConstraint(tax_id, country, name='Registration Number per country.'), UniqueConstraint(tax_id, country, name='Registration Number per country.'),
) )
@ -100,6 +95,11 @@ class Individual(JoinedTableMixin, Agent):
active_org_id = Column(UUID(as_uuid=True), ForeignKey(Organization.id)) active_org_id = Column(UUID(as_uuid=True), ForeignKey(Organization.id))
active_org = relationship(Organization, primaryjoin=active_org_id == Organization.id) active_org = relationship(Organization, primaryjoin=active_org_id == Organization.id)
user_id = Column(UUID(as_uuid=True), ForeignKey(User.id), unique=True)
user = relationship(User,
backref=backref('individuals', lazy=True, collection_class=set),
primaryjoin=user_id == User.id)
class Membership(Thing): class Membership(Thing):
"""Organizations that are related to the Individual. """Organizations that are related to the Individual.

View File

@ -727,10 +727,7 @@ class Trade(JoinedTableMixin, EventWithMultipleDevices):
If no price is set it is supposed that the trade was If no price is set it is supposed that the trade was
not payed, usual in donations. not payed, usual in donations.
""" """
to_id = Column(UUID(as_uuid=True), to_id = Column(UUID(as_uuid=True), ForeignKey(Agent.id), nullable=False)
ForeignKey(Agent.id),
nullable=False,
default=lambda: g.user.id)
# todo compute the org # todo compute the org
to = relationship(Agent, to = relationship(Agent,
backref=backref('events_to', backref=backref('events_to',
@ -738,6 +735,9 @@ class Trade(JoinedTableMixin, EventWithMultipleDevices):
collection_class=OrderedSet, collection_class=OrderedSet,
order_by=lambda: Event.created), order_by=lambda: Event.created),
primaryjoin=to_id == Agent.id) primaryjoin=to_id == Agent.id)
to_comment = """
The agent that gets the device due this deal.
"""
confirms_id = Column(UUID(as_uuid=True), ForeignKey(Organize.id)) confirms_id = Column(UUID(as_uuid=True), ForeignKey(Organize.id))
confirms = relationship(Organize, confirms = relationship(Organize,
backref=backref('confirmation', lazy=True, uselist=False), backref=backref('confirmation', lazy=True, uselist=False),

View File

@ -352,7 +352,7 @@ class Trade(EventWithMultipleDevices):
shipping_date = DateTime(data_key='shippingDate') shipping_date = DateTime(data_key='shippingDate')
invoice_number = String(validate=Length(max=STR_SIZE), data_key='invoiceNumber') invoice_number = String(validate=Length(max=STR_SIZE), data_key='invoiceNumber')
price = NestedOn(Price) price = NestedOn(Price)
to = NestedOn(Agent, only_query='id') to = NestedOn(Agent, only_query='id', required=True, comment=m.Trade.to_comment)
confirms = NestedOn(Organize) confirms = NestedOn(Organize)

View File

@ -26,9 +26,10 @@ class Tag(Thing):
primaryjoin=Organization.id == org_id, primaryjoin=Organization.id == org_id,
collection_class=set) collection_class=set)
"""The organization that issued the tag.""" """The organization that issued the tag."""
provider = Column(URL(), provider = Column(URL())
comment='The tag provider URL. If None, the provider is this Devicehub.') provider.comment = """
provider.comment = """The provider URL.""" The tag provider URL. If None, the provider is this Devicehub.
"""
device_id = Column(BigInteger, device_id = Column(BigInteger,
# We don't want to delete the tag on device deletion, only set to null # We don't want to delete the tag on device deletion, only set to null
ForeignKey(Device.id, ondelete=DB_CASCADE_SET_NULL)) ForeignKey(Device.id, ondelete=DB_CASCADE_SET_NULL))
@ -68,6 +69,13 @@ class Tag(Thing):
raise ValidationError('Tags cannot contain slashes (/).') raise ValidationError('Tags cannot contain slashes (/).')
return value return value
@validates('provider')
def use_only_domain(self, _, url: URL):
if url.path:
raise ValidationError('Provider can only contain scheme and host',
field_names=['provider'])
return url
__table_args__ = ( __table_args__ = (
UniqueConstraint(device_id, org_id, name='one_tag_per_org'), UniqueConstraint(device_id, org_id, name='one_tag_per_org'),
UniqueConstraint(secondary, org_id, name='one_secondary_per_org') UniqueConstraint(secondary, org_id, name='one_secondary_per_org')

View File

@ -1,7 +1,9 @@
from marshmallow.fields import String from marshmallow.fields import String
from sqlalchemy.util import OrderedSet
from teal.marshmallow import URL from teal.marshmallow import URL
from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.agent.schemas import Organization
from ereuse_devicehub.resources.device.schemas import Device from ereuse_devicehub.resources.device.schemas import Device
from ereuse_devicehub.resources.schemas import Thing from ereuse_devicehub.resources.schemas import Thing
from ereuse_devicehub.resources.tag import model as m from ereuse_devicehub.resources.tag import model as m
@ -19,5 +21,5 @@ class Tag(Thing):
provider = URL(description=m.Tag.provider.comment, provider = URL(description=m.Tag.provider.comment,
validator=without_slash) validator=without_slash)
device = NestedOn(Device, dump_only=True) device = NestedOn(Device, dump_only=True)
org = String() org = NestedOn(Organization, collection_class=OrderedSet, only_query='id')
secondary = String(description=m.Tag.secondary.comment) secondary = String(description=m.Tag.secondary.comment)

View File

@ -1,4 +1,5 @@
from click import argument, option from click import argument, option
from flask import current_app
from teal.resource import Converters, Resource from teal.resource import Converters, Resource
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
@ -22,14 +23,26 @@ class UserDef(Resource):
self.add_url_rule('/login', view_func=login, methods={'POST'}) self.add_url_rule('/login', view_func=login, methods={'POST'})
@argument('email') @argument('email')
@option('--password', prompt=True, hide_input=True, confirmation_prompt=True) @option('-a', '--agent', help='The name of an agent to create with the user.')
def create_user(self, email: str, password: str) -> dict: @option('-c', '--country', help='The country of the agent (if --agent is set).')
""" @option('-t', '--telephone', help='The telephone of the agent (if --agent is set).')
Creates an user. @option('-t', '--tax-id', help='The tax id of the agent (if --agent is set).')
@option('-p', '--password', prompt=True, hide_input=True, confirmation_prompt=True)
def create_user(self, email: str, password: str, agent: str = None, country: str = None,
telephone: str = None, tax_id: str = None) -> dict:
"""Creates an user.
If ``--agent`` is passed, it creates an ``Individual`` agent
that represents the user.
""" """
from ereuse_devicehub.resources.agent.models import Individual
u = self.SCHEMA(only={'email', 'password'}, exclude=('token',)) \ u = self.SCHEMA(only={'email', 'password'}, exclude=('token',)) \
.load({'email': email, 'password': password}) .load({'email': email, 'password': password})
user = User(**u) user = User(**u)
agent = Individual(**current_app.resources[Individual.t].schema.load(
dict(name=agent, email=email, country=country, telephone=telephone, taxId=tax_id)
))
user.individuals.add(agent)
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
return self.schema.dump(user) return self.schema.dump(user)

View File

@ -3,6 +3,8 @@ from uuid import UUID
import pytest import pytest
from sqlalchemy_utils import Password from sqlalchemy_utils import Password
from teal.enums import Country
from teal.marshmallow import ValidationError
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from ereuse_devicehub.client import Client from ereuse_devicehub.client import Client
@ -11,23 +13,33 @@ from ereuse_devicehub.devicehub import Devicehub
from ereuse_devicehub.resources.user import UserDef from ereuse_devicehub.resources.user import UserDef
from ereuse_devicehub.resources.user.exceptions import WrongCredentials from ereuse_devicehub.resources.user.exceptions import WrongCredentials
from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.user.models import User
from teal.marshmallow import ValidationError
from tests.conftest import app_context, create_user from tests.conftest import app_context, create_user
@pytest.mark.usefixtures(app_context.__name__) @pytest.mark.usefixtures(app_context.__name__)
def test_create_user_method(app: Devicehub): def test_create_user_method_with_agent(app: Devicehub):
""" """
Tests creating an user through the main method. Tests creating an user through the main method.
This method checks that the token is correct, too. This method checks that the token is correct, too.
""" """
user_def = app.resources['User'] # type: UserDef user_def = app.resources['User'] # type: UserDef
u = user_def.create_user(email='foo@foo.com', password='foo') u = user_def.create_user(email='foo@foo.com',
password='foo',
agent='Nice Person',
country=Country.ES.name,
telephone='+34 666 66 66 66',
tax_id='1234')
user = User.query.filter_by(id=u['id']).one() # type: User user = User.query.filter_by(id=u['id']).one() # type: User
assert user.email == 'foo@foo.com' assert user.email == 'foo@foo.com'
assert isinstance(user.token, UUID) assert isinstance(user.token, UUID)
assert User.query.filter_by(email='foo@foo.com').one() == user assert User.query.filter_by(email='foo@foo.com').one() == user
individual = next(iter(user.individuals))
assert individual.name == 'Nice Person'
assert individual.tax_id == '1234'
assert individual.telephone.e164 == '+34666666666'
assert individual.country == Country.ES
assert individual.email == user.email
@pytest.mark.usefixtures(app_context.__name__) @pytest.mark.usefixtures(app_context.__name__)