add user model fields #3017

This commit is contained in:
Cayo Puigdefabregas 2022-04-06 12:35:08 +02:00
parent a7d5a3917d
commit 3114c678a4
5 changed files with 182 additions and 72 deletions

View File

@ -0,0 +1,35 @@
"""add new fields in agent
Revision ID: 1b61613d1c19
Revises: 8571fb32c912
Create Date: 2022-04-06 12:23:37.644108
"""
import citext
import sqlalchemy as sa
from alembic import context, op
# revision identifiers, used by Alembic.
revision = '1b61613d1c19'
down_revision = '8571fb32c912'
branch_labels = None
depends_on = None
def get_inv():
INV = context.get_x_argument(as_dictionary=True).get('inventory')
if not INV:
raise ValueError("Inventory value is not specified")
return INV
def upgrade():
op.add_column(
"agent",
sa.Column("last_name", citext.CIText(), nullable=True),
schema=f'{get_inv()}',
)
def downgrade():
op.drop_column('agent', 'last_name', schema=f'{get_inv()}')

View File

@ -3,7 +3,9 @@ from operator import attrgetter
from uuid import uuid4 from uuid import uuid4
from citext import CIText from citext import CIText
from sqlalchemy import Column, Enum as DBEnum, ForeignKey, Unicode, UniqueConstraint from sqlalchemy import Column
from sqlalchemy import Enum as DBEnum
from sqlalchemy import ForeignKey, Unicode, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import backref, relationship, validates from sqlalchemy.orm import backref, relationship, validates
@ -29,6 +31,7 @@ class Agent(Thing):
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
type = Column(Unicode, nullable=False) type = Column(Unicode, nullable=False)
name = Column(CIText()) name = Column(CIText())
last_name = Column(CIText())
name.comment = """The name of the organization or person.""" name.comment = """The name of the organization or person."""
tax_id = Column(Unicode(length=STR_SM_SIZE), check_lower('tax_id')) tax_id = Column(Unicode(length=STR_SM_SIZE), check_lower('tax_id'))
tax_id.comment = """The Tax / Fiscal ID of the organization, tax_id.comment = """The Tax / Fiscal ID of the organization,
@ -42,9 +45,15 @@ class Agent(Thing):
__table_args__ = ( __table_args__ = (
UniqueConstraint(tax_id, country, name='Registration Number per country.'), UniqueConstraint(tax_id, country, name='Registration Number per country.'),
UniqueConstraint(tax_id, name, name='One tax ID with one name.'), UniqueConstraint(tax_id, name, name='One tax ID with one name.'),
db.Index('agent_type', type, postgresql_using='hash') db.Index('agent_type', type, postgresql_using='hash'),
) )
@property
def get_full_name(self):
if self.last_name:
return "{} {}".format(self.name, self.last_name)
return self.name
@declared_attr @declared_attr
def __mapper_args__(cls): def __mapper_args__(cls):
"""Defines inheritance. """Defines inheritance.
@ -63,7 +72,9 @@ class Agent(Thing):
@property @property
def actions(self) -> list: def actions(self) -> list:
# todo test # todo test
return sorted(chain(self.actions_agent, self.actions_to), key=attrgetter('created')) return sorted(
chain(self.actions_agent, self.actions_to), key=attrgetter('created')
)
@validates('name') @validates('name')
def does_not_contain_slash(self, _, value: str): def does_not_contain_slash(self, _, value: str):
@ -76,15 +87,17 @@ class Agent(Thing):
class Organization(JoinedTableMixin, Agent): class Organization(JoinedTableMixin, Agent):
default_of = db.relationship(Inventory, default_of = db.relationship(
uselist=False, Inventory,
lazy=True, uselist=False,
backref=backref('org', lazy=True), lazy=True,
# We need to use this as we cannot do Inventory.foreign -> Org backref=backref('org', lazy=True),
# as foreign keys can only reference to one table # We need to use this as we cannot do Inventory.foreign -> Org
# and we have multiple organization table (one per schema) # as foreign keys can only reference to one table
foreign_keys=[Inventory.org_id], # and we have multiple organization table (one per schema)
primaryjoin=lambda: Organization.id == Inventory.org_id) foreign_keys=[Inventory.org_id],
primaryjoin=lambda: Organization.id == Inventory.org_id,
)
def __init__(self, name: str, **kwargs) -> None: def __init__(self, name: str, **kwargs) -> None:
super().__init__(**kwargs, name=name) super().__init__(**kwargs, name=name)
@ -97,12 +110,17 @@ class Organization(JoinedTableMixin, Agent):
class Individual(JoinedTableMixin, Agent): 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_id = Column(UUID(as_uuid=True), ForeignKey(User.id), unique=True)
user = relationship(User, user = relationship(
backref=backref('individuals', lazy=True, collection_class=set), User,
primaryjoin=user_id == User.id) backref=backref('individuals', lazy=True, collection_class=set),
primaryjoin=user_id == User.id,
)
class Membership(Thing): class Membership(Thing):
@ -110,20 +128,29 @@ class Membership(Thing):
For example, because the individual works in or because is a member of. For example, because the individual works in or because is a member of.
""" """
id = Column(Unicode(), check_lower('id'))
organization_id = Column(UUID(as_uuid=True), ForeignKey(Organization.id), primary_key=True)
organization = relationship(Organization,
backref=backref('members', collection_class=set, lazy=True),
primaryjoin=organization_id == Organization.id)
individual_id = Column(UUID(as_uuid=True), ForeignKey(Individual.id), primary_key=True)
individual = relationship(Individual,
backref=backref('member_of', collection_class=set, lazy=True),
primaryjoin=individual_id == Individual.id)
def __init__(self, organization: Organization, individual: Individual, id: str = None) -> None: id = Column(Unicode(), check_lower('id'))
super().__init__(organization=organization, organization_id = Column(
individual=individual, UUID(as_uuid=True), ForeignKey(Organization.id), primary_key=True
id=id) )
organization = relationship(
Organization,
backref=backref('members', collection_class=set, lazy=True),
primaryjoin=organization_id == Organization.id,
)
individual_id = Column(
UUID(as_uuid=True), ForeignKey(Individual.id), primary_key=True
)
individual = relationship(
Individual,
backref=backref('member_of', collection_class=set, lazy=True),
primaryjoin=individual_id == Individual.id,
)
def __init__(
self, organization: Organization, individual: Individual, id: str = None
) -> None:
super().__init__(organization=organization, individual=individual, id=id)
__table_args__ = ( __table_args__ = (
UniqueConstraint(id, organization_id, name='One member id per organization.'), UniqueConstraint(id, organization_id, name='One member id per organization.'),
@ -134,6 +161,7 @@ class Person(Individual):
"""A person in the system. There can be several persons pointing to """A person in the system. There can be several persons pointing to
a real. a real.
""" """
pass pass

View File

@ -2,37 +2,44 @@ from uuid import uuid4
from flask import current_app as app from flask import current_app as app
from flask_login import UserMixin from flask_login import UserMixin
from sqlalchemy import Column, Boolean, BigInteger, Sequence from sqlalchemy import BigInteger, Boolean, Column, Sequence
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy_utils import EmailType, PasswordType from sqlalchemy_utils import EmailType, PasswordType
from teal.db import IntEnum from teal.db import IntEnum
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.resources.enums import SessionType
from ereuse_devicehub.resources.inventory.model import Inventory from ereuse_devicehub.resources.inventory.model import Inventory
from ereuse_devicehub.resources.models import STR_SIZE, Thing from ereuse_devicehub.resources.models import STR_SIZE, Thing
from ereuse_devicehub.resources.enums import SessionType
class User(UserMixin, Thing): class User(UserMixin, Thing):
__table_args__ = {'schema': 'common'} __table_args__ = {'schema': 'common'}
id = Column(UUID(as_uuid=True), default=uuid4, primary_key=True) id = Column(UUID(as_uuid=True), default=uuid4, primary_key=True)
email = Column(EmailType, nullable=False, unique=True) email = Column(EmailType, nullable=False, unique=True)
password = Column(PasswordType(max_length=STR_SIZE, password = Column(
onload=lambda **kwargs: dict( PasswordType(
schemes=app.config['PASSWORD_SCHEMES'], max_length=STR_SIZE,
**kwargs onload=lambda **kwargs: dict(
))) schemes=app.config['PASSWORD_SCHEMES'], **kwargs
),
)
)
token = Column(UUID(as_uuid=True), default=uuid4, unique=True, nullable=False) token = Column(UUID(as_uuid=True), default=uuid4, unique=True, nullable=False)
active = Column(Boolean, default=True, nullable=False) active = Column(Boolean, default=True, nullable=False)
phantom = Column(Boolean, default=False, nullable=False) phantom = Column(Boolean, default=False, nullable=False)
inventories = db.relationship(Inventory, inventories = db.relationship(
backref=db.backref('users', lazy=True, collection_class=set), Inventory,
secondary=lambda: UserInventory.__table__, backref=db.backref('users', lazy=True, collection_class=set),
collection_class=set) secondary=lambda: UserInventory.__table__,
collection_class=set,
)
# todo set restriction that user has, at least, one active db # todo set restriction that user has, at least, one active db
def __init__(self, email, password=None, inventories=None, active=True, phantom=False) -> None: def __init__(
self, email, password=None, inventories=None, active=True, phantom=False
) -> None:
"""Creates an user. """Creates an user.
:param email: :param email:
:param password: :param password:
@ -44,8 +51,13 @@ class User(UserMixin, Thing):
create during the trade actions create during the trade actions
""" """
inventories = inventories or {Inventory.current} inventories = inventories or {Inventory.current}
super().__init__(email=email, password=password, inventories=inventories, super().__init__(
active=active, phantom=phantom) email=email,
password=password,
inventories=inventories,
active=active,
phantom=phantom,
)
def __repr__(self) -> str: def __repr__(self) -> str:
return '<User {0.email}>'.format(self) return '<User {0.email}>'.format(self)
@ -73,8 +85,9 @@ class User(UserMixin, Thing):
@property @property
def get_full_name(self): def get_full_name(self):
# TODO(@slamora) create first_name & last_name fields and use if self.individual:
# them to generate user full name return self.individual.get_full_name
return self.email return self.email
def check_password(self, password): def check_password(self, password):
@ -84,9 +97,12 @@ class User(UserMixin, Thing):
class UserInventory(db.Model): class UserInventory(db.Model):
"""Relationship between users and their inventories.""" """Relationship between users and their inventories."""
__table_args__ = {'schema': 'common'} __table_args__ = {'schema': 'common'}
user_id = db.Column(db.UUID(as_uuid=True), db.ForeignKey(User.id), primary_key=True) user_id = db.Column(db.UUID(as_uuid=True), db.ForeignKey(User.id), primary_key=True)
inventory_id = db.Column(db.Unicode(), db.ForeignKey(Inventory.id), primary_key=True) inventory_id = db.Column(
db.Unicode(), db.ForeignKey(Inventory.id), primary_key=True
)
class Session(Thing): class Session(Thing):
@ -96,9 +112,11 @@ class Session(Thing):
token = Column(UUID(as_uuid=True), default=uuid4, unique=True, nullable=False) token = Column(UUID(as_uuid=True), default=uuid4, unique=True, nullable=False)
type = Column(IntEnum(SessionType), default=SessionType.Internal, nullable=False) type = Column(IntEnum(SessionType), default=SessionType.Internal, nullable=False)
user_id = db.Column(db.UUID(as_uuid=True), db.ForeignKey(User.id)) user_id = db.Column(db.UUID(as_uuid=True), db.ForeignKey(User.id))
user = db.relationship(User, user = db.relationship(
backref=db.backref('sessions', lazy=True, collection_class=set), User,
collection_class=set) backref=db.backref('sessions', lazy=True, collection_class=set),
collection_class=set,
)
def __str__(self) -> str: def __str__(self) -> str:
return '{0.token}'.format(self) return '{0.token}'.format(self)

View File

@ -27,7 +27,7 @@
</div> </div>
<div class="col-xl-8 d-none"><!-- TODO (hidden until is implemented )--> <div class="col-xl-8"><!-- TODO (hidden until is implemented )-->
<div class="card"> <div class="card">
<div class="card-body pt-3"> <div class="card-body pt-3">
@ -54,46 +54,73 @@
<div class="tab-content pt-2"> <div class="tab-content pt-2">
<div class="tab-pane fade show active profile-overview" id="profile-overview"> <div class="tab-pane fade show active profile-overview" id="profile-overview">
<h5 class="card-title">About</h5>
<p class="small fst-italic">Sunt est soluta temporibus accusantium neque nam maiores cumque temporibus. Tempora libero non est unde veniam est qui dolor. Ut sunt iure rerum quae quisquam autem eveniet perspiciatis odit. Fuga sequi sed ea saepe at unde.</p>
<h5 class="card-title">Profile Details</h5> <h5 class="card-title">Profile Details</h5>
<div class="row">
<div class="col-lg-3 col-md-4 label">Account user</div>
<div class="col-lg-9 col-md-8">{{ current_user.email or ''}}</div>
</div>
{% for u in current_user.individuals %}
<div class="row"> <div class="row">
<div class="col-lg-3 col-md-4 label ">Full Name</div> <div class="col-lg-3 col-md-4 label ">Full Name</div>
<div class="col-lg-9 col-md-8">Kevin Anderson</div> <div class="col-lg-9 col-md-8">{{ u.get_full_name or ''}}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Company</div>
<div class="col-lg-9 col-md-8">Lueilwitz, Wisoky and Leuschke</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Job</div>
<div class="col-lg-9 col-md-8">Web Designer</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-3 col-md-4 label">Country</div> <div class="col-lg-3 col-md-4 label">Country</div>
<div class="col-lg-9 col-md-8">USA</div> <div class="col-lg-9 col-md-8">{{ u.country or ''}}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Address</div>
<div class="col-lg-9 col-md-8">A108 Adam Street, New York, NY 535022</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-3 col-md-4 label">Phone</div> <div class="col-lg-3 col-md-4 label">Phone</div>
<div class="col-lg-9 col-md-8">(436) 486-3538 x29071</div> <div class="col-lg-9 col-md-8">{{ u.telephone or ''}}</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-3 col-md-4 label">Email</div> <div class="col-lg-3 col-md-4 label">Email</div>
<div class="col-lg-9 col-md-8">k.anderson@example.com</div> <div class="col-lg-9 col-md-8">{{ u.email or ''}}</div>
</div> </div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Created</div>
<div class="col-lg-9 col-md-8">{{ u.created.strftime('%H:%M %d-%m-%Y') or ''}}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Last login</div>
<div class="col-lg-9 col-md-8">
{% for s in sessions %}
{{ s }}<br />
{% endfor %}
</div>
</div>
{% if u.active_org %}
<div class="row">
<div class="col-lg-3 col-md-4 label">Company name</div>
<div class="col-lg-9 col-md-8">{{ u.active_org.name or ''}}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Company country</div>
<div class="col-lg-9 col-md-8">{{ u.active_org.country or '' }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Company Phone</div>
<div class="col-lg-9 col-md-8">{{ u.active_org.telephone or '' }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Company Email</div>
<div class="col-lg-9 col-md-8">{{ u.active_org.email or '' }}</div>
</div>
{% endif %}
{% endfor %}
</div> </div>
<div class="tab-pane fade profile-edit pt-3" id="profile-edit"> <div class="tab-pane fade profile-edit pt-3" id="profile-edit">

View File

@ -50,8 +50,10 @@ class UserProfileView(View):
template_name = 'ereuse_devicehub/user_profile.html' template_name = 'ereuse_devicehub/user_profile.html'
def dispatch_request(self): def dispatch_request(self):
sessions = {s.created.strftime('%H:%M %d-%m-%Y') for s in current_user.sessions}
context = { context = {
'current_user': current_user, 'current_user': current_user,
'sessions': sessions,
'version': __version__, 'version': __version__,
} }
return flask.render_template(self.template_name, **context) return flask.render_template(self.template_name, **context)