From f56ca473e8f0a2deeb86ceaf1ac099510110cacb Mon Sep 17 00:00:00 2001 From: Xavier Bustamante Talavera Date: Thu, 20 Sep 2018 16:40:41 +0200 Subject: [PATCH] Create eTags, lots, and events in dummy --- ereuse_devicehub/dummy/dummy.py | 59 ++++++++++++++++++-- ereuse_devicehub/resources/agent/__init__.py | 6 +- ereuse_devicehub/resources/agent/models.py | 10 ++-- ereuse_devicehub/resources/event/models.py | 8 +-- ereuse_devicehub/resources/event/schemas.py | 2 +- ereuse_devicehub/resources/tag/model.py | 14 ++++- ereuse_devicehub/resources/tag/schema.py | 4 +- ereuse_devicehub/resources/user/__init__.py | 21 +++++-- tests/test_user.py | 18 +++++- 9 files changed, 114 insertions(+), 28 deletions(-) diff --git a/ereuse_devicehub/dummy/dummy.py b/ereuse_devicehub/dummy/dummy.py index 52341f12..f4ffac78 100644 --- a/ereuse_devicehub/dummy/dummy.py +++ b/ereuse_devicehub/dummy/dummy.py @@ -1,4 +1,7 @@ +import itertools +import json from pathlib import Path +from typing import Set import click import click_spinner @@ -7,22 +10,26 @@ import yaml from ereuse_devicehub.client import UserClient from ereuse_devicehub.db import db 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.tag.model import Tag from ereuse_devicehub.resources.user import User class Dummy: - SNAPSHOTS = ( - 'workbench-server-1', - 'computer-monitor' - ) TAGS = ( 'tag1', 'tag2', '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: super().__init__() @@ -34,20 +41,60 @@ class Dummy: 'Do you want to continue?') def run(self): print('Preparing the database...'.ljust(30), end='') + runner = self.app.test_cli_runner() with click_spinner.spinner(): 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') + # todo put user's agent into Org for id in self.TAGS: 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()) print('done.') + pcs = set() # type: Set[int] with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar: for path in bar: with path.open() as 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) 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.') def user_client(self, email: str, password: str): diff --git a/ereuse_devicehub/resources/agent/__init__.py b/ereuse_devicehub/resources/agent/__init__.py index ac4e6169..460f49bd 100644 --- a/ereuse_devicehub/resources/agent/__init__.py +++ b/ereuse_devicehub/resources/agent/__init__.py @@ -1,3 +1,5 @@ +import json + import click from flask import current_app as app from teal.db import SQLAlchemy @@ -39,7 +41,9 @@ class OrganizationDef(AgentDef): )) db.session.add(org) 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): """Creates the default organization.""" diff --git a/ereuse_devicehub/resources/agent/models.py b/ereuse_devicehub/resources/agent/models.py index 33e75a84..9727a711 100644 --- a/ereuse_devicehub/resources/agent/models.py +++ b/ereuse_devicehub/resources/agent/models.py @@ -43,11 +43,6 @@ class Agent(Thing): telephone = Column(PhoneNumberType()) 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__ = ( 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 = 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): """Organizations that are related to the Individual. diff --git a/ereuse_devicehub/resources/event/models.py b/ereuse_devicehub/resources/event/models.py index 80a910bb..5c041625 100644 --- a/ereuse_devicehub/resources/event/models.py +++ b/ereuse_devicehub/resources/event/models.py @@ -727,10 +727,7 @@ class Trade(JoinedTableMixin, EventWithMultipleDevices): If no price is set it is supposed that the trade was not payed, usual in donations. """ - to_id = Column(UUID(as_uuid=True), - ForeignKey(Agent.id), - nullable=False, - default=lambda: g.user.id) + to_id = Column(UUID(as_uuid=True), ForeignKey(Agent.id), nullable=False) # todo compute the org to = relationship(Agent, backref=backref('events_to', @@ -738,6 +735,9 @@ class Trade(JoinedTableMixin, EventWithMultipleDevices): collection_class=OrderedSet, order_by=lambda: Event.created), 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 = relationship(Organize, backref=backref('confirmation', lazy=True, uselist=False), diff --git a/ereuse_devicehub/resources/event/schemas.py b/ereuse_devicehub/resources/event/schemas.py index 853640ed..372fd7d4 100644 --- a/ereuse_devicehub/resources/event/schemas.py +++ b/ereuse_devicehub/resources/event/schemas.py @@ -352,7 +352,7 @@ class Trade(EventWithMultipleDevices): shipping_date = DateTime(data_key='shippingDate') invoice_number = String(validate=Length(max=STR_SIZE), data_key='invoiceNumber') 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) diff --git a/ereuse_devicehub/resources/tag/model.py b/ereuse_devicehub/resources/tag/model.py index 4082f7b6..3345bac1 100644 --- a/ereuse_devicehub/resources/tag/model.py +++ b/ereuse_devicehub/resources/tag/model.py @@ -26,9 +26,10 @@ class Tag(Thing): primaryjoin=Organization.id == org_id, collection_class=set) """The organization that issued the tag.""" - provider = Column(URL(), - comment='The tag provider URL. If None, the provider is this Devicehub.') - provider.comment = """The provider URL.""" + provider = Column(URL()) + provider.comment = """ + The tag provider URL. If None, the provider is this Devicehub. + """ device_id = Column(BigInteger, # We don't want to delete the tag on device deletion, only set to null ForeignKey(Device.id, ondelete=DB_CASCADE_SET_NULL)) @@ -68,6 +69,13 @@ class Tag(Thing): raise ValidationError('Tags cannot contain slashes (/).') 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__ = ( UniqueConstraint(device_id, org_id, name='one_tag_per_org'), UniqueConstraint(secondary, org_id, name='one_secondary_per_org') diff --git a/ereuse_devicehub/resources/tag/schema.py b/ereuse_devicehub/resources/tag/schema.py index 8cf0d909..ec6d0cb4 100644 --- a/ereuse_devicehub/resources/tag/schema.py +++ b/ereuse_devicehub/resources/tag/schema.py @@ -1,7 +1,9 @@ from marshmallow.fields import String +from sqlalchemy.util import OrderedSet from teal.marshmallow import URL 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.schemas import Thing from ereuse_devicehub.resources.tag import model as m @@ -19,5 +21,5 @@ class Tag(Thing): provider = URL(description=m.Tag.provider.comment, validator=without_slash) device = NestedOn(Device, dump_only=True) - org = String() + org = NestedOn(Organization, collection_class=OrderedSet, only_query='id') secondary = String(description=m.Tag.secondary.comment) diff --git a/ereuse_devicehub/resources/user/__init__.py b/ereuse_devicehub/resources/user/__init__.py index 9a980faa..9c194b44 100644 --- a/ereuse_devicehub/resources/user/__init__.py +++ b/ereuse_devicehub/resources/user/__init__.py @@ -1,4 +1,5 @@ from click import argument, option +from flask import current_app from teal.resource import Converters, Resource from ereuse_devicehub.db import db @@ -22,14 +23,26 @@ class UserDef(Resource): self.add_url_rule('/login', view_func=login, methods={'POST'}) @argument('email') - @option('--password', prompt=True, hide_input=True, confirmation_prompt=True) - def create_user(self, email: str, password: str) -> dict: - """ - Creates an user. + @option('-a', '--agent', help='The name of an agent to create with the user.') + @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).') + @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',)) \ .load({'email': email, 'password': password}) 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.commit() return self.schema.dump(user) diff --git a/tests/test_user.py b/tests/test_user.py index ca362ff4..7a8410b5 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -3,6 +3,8 @@ from uuid import UUID import pytest from sqlalchemy_utils import Password +from teal.enums import Country +from teal.marshmallow import ValidationError from werkzeug.exceptions import NotFound 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.exceptions import WrongCredentials from ereuse_devicehub.resources.user.models import User -from teal.marshmallow import ValidationError from tests.conftest import app_context, create_user @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. This method checks that the token is correct, too. """ 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 assert user.email == 'foo@foo.com' assert isinstance(user.token, UUID) 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__)