add user model fields #3017
This commit is contained in:
parent
a7d5a3917d
commit
3114c678a4
|
@ -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()}')
|
|
@ -3,7 +3,9 @@ from operator import attrgetter
|
|||
from uuid import uuid4
|
||||
|
||||
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.ext.declarative import declared_attr
|
||||
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)
|
||||
type = Column(Unicode, nullable=False)
|
||||
name = Column(CIText())
|
||||
last_name = Column(CIText())
|
||||
name.comment = """The name of the organization or person."""
|
||||
tax_id = Column(Unicode(length=STR_SM_SIZE), check_lower('tax_id'))
|
||||
tax_id.comment = """The Tax / Fiscal ID of the organization,
|
||||
|
@ -42,9 +45,15 @@ class Agent(Thing):
|
|||
__table_args__ = (
|
||||
UniqueConstraint(tax_id, country, name='Registration Number per country.'),
|
||||
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
|
||||
def __mapper_args__(cls):
|
||||
"""Defines inheritance.
|
||||
|
@ -63,7 +72,9 @@ class Agent(Thing):
|
|||
@property
|
||||
def actions(self) -> list:
|
||||
# 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')
|
||||
def does_not_contain_slash(self, _, value: str):
|
||||
|
@ -76,7 +87,8 @@ class Agent(Thing):
|
|||
|
||||
|
||||
class Organization(JoinedTableMixin, Agent):
|
||||
default_of = db.relationship(Inventory,
|
||||
default_of = db.relationship(
|
||||
Inventory,
|
||||
uselist=False,
|
||||
lazy=True,
|
||||
backref=backref('org', lazy=True),
|
||||
|
@ -84,7 +96,8 @@ class Organization(JoinedTableMixin, Agent):
|
|||
# as foreign keys can only reference to one table
|
||||
# and we have multiple organization table (one per schema)
|
||||
foreign_keys=[Inventory.org_id],
|
||||
primaryjoin=lambda: Organization.id == Inventory.org_id)
|
||||
primaryjoin=lambda: Organization.id == Inventory.org_id,
|
||||
)
|
||||
|
||||
def __init__(self, name: str, **kwargs) -> None:
|
||||
super().__init__(**kwargs, name=name)
|
||||
|
@ -97,12 +110,17 @@ class Organization(JoinedTableMixin, Agent):
|
|||
|
||||
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)
|
||||
|
||||
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,
|
||||
user = relationship(
|
||||
User,
|
||||
backref=backref('individuals', lazy=True, collection_class=set),
|
||||
primaryjoin=user_id == User.id)
|
||||
primaryjoin=user_id == User.id,
|
||||
)
|
||||
|
||||
|
||||
class Membership(Thing):
|
||||
|
@ -110,20 +128,29 @@ class Membership(Thing):
|
|||
|
||||
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:
|
||||
super().__init__(organization=organization,
|
||||
individual=individual,
|
||||
id=id)
|
||||
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:
|
||||
super().__init__(organization=organization, individual=individual, id=id)
|
||||
|
||||
__table_args__ = (
|
||||
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 real.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -2,37 +2,44 @@ from uuid import uuid4
|
|||
|
||||
from flask import current_app as app
|
||||
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_utils import EmailType, PasswordType
|
||||
from teal.db import IntEnum
|
||||
|
||||
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.models import STR_SIZE, Thing
|
||||
from ereuse_devicehub.resources.enums import SessionType
|
||||
|
||||
|
||||
class User(UserMixin, Thing):
|
||||
__table_args__ = {'schema': 'common'}
|
||||
id = Column(UUID(as_uuid=True), default=uuid4, primary_key=True)
|
||||
email = Column(EmailType, nullable=False, unique=True)
|
||||
password = Column(PasswordType(max_length=STR_SIZE,
|
||||
password = Column(
|
||||
PasswordType(
|
||||
max_length=STR_SIZE,
|
||||
onload=lambda **kwargs: dict(
|
||||
schemes=app.config['PASSWORD_SCHEMES'],
|
||||
**kwargs
|
||||
)))
|
||||
schemes=app.config['PASSWORD_SCHEMES'], **kwargs
|
||||
),
|
||||
)
|
||||
)
|
||||
token = Column(UUID(as_uuid=True), default=uuid4, unique=True, nullable=False)
|
||||
active = Column(Boolean, default=True, nullable=False)
|
||||
phantom = Column(Boolean, default=False, nullable=False)
|
||||
inventories = db.relationship(Inventory,
|
||||
inventories = db.relationship(
|
||||
Inventory,
|
||||
backref=db.backref('users', lazy=True, collection_class=set),
|
||||
secondary=lambda: UserInventory.__table__,
|
||||
collection_class=set)
|
||||
collection_class=set,
|
||||
)
|
||||
|
||||
# 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.
|
||||
:param email:
|
||||
:param password:
|
||||
|
@ -44,8 +51,13 @@ class User(UserMixin, Thing):
|
|||
create during the trade actions
|
||||
"""
|
||||
inventories = inventories or {Inventory.current}
|
||||
super().__init__(email=email, password=password, inventories=inventories,
|
||||
active=active, phantom=phantom)
|
||||
super().__init__(
|
||||
email=email,
|
||||
password=password,
|
||||
inventories=inventories,
|
||||
active=active,
|
||||
phantom=phantom,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<User {0.email}>'.format(self)
|
||||
|
@ -73,8 +85,9 @@ class User(UserMixin, Thing):
|
|||
|
||||
@property
|
||||
def get_full_name(self):
|
||||
# TODO(@slamora) create first_name & last_name fields and use
|
||||
# them to generate user full name
|
||||
if self.individual:
|
||||
return self.individual.get_full_name
|
||||
|
||||
return self.email
|
||||
|
||||
def check_password(self, password):
|
||||
|
@ -84,9 +97,12 @@ class User(UserMixin, Thing):
|
|||
|
||||
class UserInventory(db.Model):
|
||||
"""Relationship between users and their inventories."""
|
||||
|
||||
__table_args__ = {'schema': 'common'}
|
||||
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):
|
||||
|
@ -96,9 +112,11 @@ class Session(Thing):
|
|||
token = Column(UUID(as_uuid=True), default=uuid4, unique=True, 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 = db.relationship(User,
|
||||
user = db.relationship(
|
||||
User,
|
||||
backref=db.backref('sessions', lazy=True, collection_class=set),
|
||||
collection_class=set)
|
||||
collection_class=set,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{0.token}'.format(self)
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
</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-body pt-3">
|
||||
|
@ -54,46 +54,73 @@
|
|||
<div class="tab-content pt-2">
|
||||
|
||||
<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>
|
||||
|
||||
<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="col-lg-3 col-md-4 label ">Full Name</div>
|
||||
<div class="col-lg-9 col-md-8">Kevin Anderson</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 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">Country</div>
|
||||
<div class="col-lg-9 col-md-8">USA</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 class="col-lg-9 col-md-8">{{ u.country or ''}}</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<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 class="row">
|
||||
<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 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 class="tab-pane fade profile-edit pt-3" id="profile-edit">
|
||||
|
|
|
@ -50,8 +50,10 @@ class UserProfileView(View):
|
|||
template_name = 'ereuse_devicehub/user_profile.html'
|
||||
|
||||
def dispatch_request(self):
|
||||
sessions = {s.created.strftime('%H:%M %d-%m-%Y') for s in current_user.sessions}
|
||||
context = {
|
||||
'current_user': current_user,
|
||||
'sessions': sessions,
|
||||
'version': __version__,
|
||||
}
|
||||
return flask.render_template(self.template_name, **context)
|
||||
|
|
Reference in New Issue