Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update organizaton #1424

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .ds.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -341,15 +341,15 @@
"filename": "tests/app/user/test_rest.py",
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
"is_verified": false,
"line_number": 108,
"line_number": 110,
"is_secret": false
},
{
"type": "Secret Keyword",
"filename": "tests/app/user/test_rest.py",
"hashed_secret": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
"is_verified": false,
"line_number": 826,
"line_number": 837,
"is_secret": false
}
],
Expand Down Expand Up @@ -384,5 +384,5 @@
}
]
},
"generated_at": "2024-10-31T21:25:32Z"
"generated_at": "2024-11-20T22:41:41Z"
}
6 changes: 3 additions & 3 deletions app/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
from app.models import (
AnnualBilling,
Domain,
EmailBranding,
Email_Branding,
Notification,
Organization,
Service,
Expand Down Expand Up @@ -338,8 +338,8 @@ def boolean_or_none(field):
email_branding = None
email_branding_column = columns[5].strip()
if len(email_branding_column) > 0:
stmt = select(EmailBranding).where(
EmailBranding.name == email_branding_column
stmt = select(Email_Branding).where(
Email_Branding.name == email_branding_column
)
email_branding = db.session.execute(stmt).scalars().one()
data = {
Expand Down
8 changes: 4 additions & 4 deletions app/dao/annual_billing_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,17 @@ def dao_get_all_free_sms_fragment_limit(service_id):

def set_default_free_allowance_for_service(service, year_start=None):
default_free_sms_fragment_limits = {
OrganizationType.FEDERAL: {
OrganizationType.federal: {
2020: 250_000,
2021: 150_000,
2022: 40_000,
},
OrganizationType.STATE: {
OrganizationType.state: {
2020: 250_000,
2021: 150_000,
2022: 40_000,
},
OrganizationType.OTHER: {
OrganizationType.other: {
2020: 250_000,
2021: 150_000,
2022: 40_000,
Expand All @@ -108,7 +108,7 @@ def set_default_free_allowance_for_service(service, year_start=None):
f"no organization type for service {service.id}. Using other default of "
f"{default_free_sms_fragment_limits['other'][year_start]}"
)
free_allowance = default_free_sms_fragment_limits[OrganizationType.OTHER][
free_allowance = default_free_sms_fragment_limits[OrganizationType.other][
year_start
]

Expand Down
8 changes: 4 additions & 4 deletions app/dao/email_branding_dao.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
from app import db
from app.dao.dao_utils import autocommit
from app.models import EmailBranding
from app.models import Email_Branding


def dao_get_email_branding_options():
return EmailBranding.query.all()
return Email_Branding.query.all()


def dao_get_email_branding_by_id(email_branding_id):
return EmailBranding.query.filter_by(id=email_branding_id).one()
return Email_Branding.query.filter_by(id=email_branding_id).one()


def dao_get_email_branding_by_name(email_branding_name):
return EmailBranding.query.filter_by(name=email_branding_name).first()
return Email_Branding.query.filter_by(name=email_branding_name).first()


@autocommit
Expand Down
6 changes: 4 additions & 2 deletions app/dao/organization_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,10 @@ def dao_add_user_to_organization(organization_id, user_id):
organization = dao_get_organization_by_id(organization_id)
stmt = select(User).filter_by(id=user_id)
user = db.session.execute(stmt).scalars().one()
user.organizations.append(organization)
db.session.add(organization)
print(f"ORG TO ADD {organization} USER ORGS {user.organizations}")
if organization not in user.organizations:
user.organizations.append(organization)
db.session.add(organization)
return user


Expand Down
4 changes: 2 additions & 2 deletions app/email_branding/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
post_update_email_branding_schema,
)
from app.errors import register_errors
from app.models import EmailBranding
from app.models import Email_Branding
from app.schema_validation import validate

email_branding_blueprint = Blueprint("email_branding", __name__)
Expand All @@ -36,7 +36,7 @@ def create_email_branding():

validate(data, post_create_email_branding_schema)

email_branding = EmailBranding(**data)
email_branding = Email_Branding(**data)
if "text" not in data.keys():
email_branding.text = email_branding.name

Expand Down
6 changes: 3 additions & 3 deletions app/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ class CallbackType(StrEnum):


class OrganizationType(StrEnum):
FEDERAL = "federal"
STATE = "state"
OTHER = "other"
federal = "federal"
state = "state"
other = "other"


class NotificationStatus(StrEnum):
Expand Down
150 changes: 98 additions & 52 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import itertools
import uuid
from typing import List, Optional

from flask import current_app, url_for
from sqlalchemy import CheckConstraint, Index, UniqueConstraint
from sqlalchemy import (
Boolean,
CheckConstraint,
DateTime,
Enum,
Float,
ForeignKey,
Index,
String,
Text,
UniqueConstraint,
func,
)
from sqlalchemy.dialects.postgresql import JSON, JSONB, UUID
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import validates
from sqlalchemy.orm import (
Mapped,
declarative_base,
mapped_column,
relationship,
validates,
)
from sqlalchemy.orm.collections import attribute_mapped_collection

from app import db, encryption
Expand Down Expand Up @@ -106,7 +125,9 @@ def update_from_original(self, original):
class User(db.Model):
__tablename__ = "users"

id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
id: Mapped[UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
name = db.Column(db.String, nullable=False, index=True, unique=False)
email_address = db.Column(db.String(255), nullable=False, index=True, unique=True)
login_uuid = db.Column(db.Text, nullable=True, index=True, unique=True)
Expand Down Expand Up @@ -160,10 +181,8 @@ class User(db.Model):
)

services = db.relationship("Service", secondary="user_to_service", backref="users")
organizations = db.relationship(
"Organization",
secondary="user_to_organization",
backref="users",
organizations: Mapped[List["Organization"]] = relationship(
"Organization", secondary="user_to_organization", backref="users", lazy="select"
)

@validates("mobile_number")
Expand Down Expand Up @@ -300,9 +319,11 @@ class ServiceUser(db.Model):
)


class EmailBranding(db.Model):
class Email_Branding(db.Model):
__tablename__ = "email_branding"
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
id: Mapped[UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
colour = db.Column(db.String(7), nullable=True)
logo = db.Column(db.String(255), nullable=True)
name = db.Column(db.String(255), unique=True, nullable=False)
Expand Down Expand Up @@ -358,68 +379,93 @@ class Domain(db.Model):
)


Base = declarative_base()


class Organization(db.Model):

# TODO When everything in this file is upgraded to 2.0 syntax,
# replace all uses of db.Model with Base
# class Organization(Base):
__tablename__ = "organization"
id = db.Column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=False

id: Mapped[UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
name = db.Column(db.String(255), nullable=False, unique=True, index=True)
active = db.Column(db.Boolean, nullable=False, default=True)
created_at = db.Column(
db.DateTime,
nullable=False,
default=utc_now(),
name: Mapped[str] = mapped_column(
String(255), nullable=False, unique=True, index=True
)
updated_at = db.Column(
db.DateTime,
nullable=True,
onupdate=utc_now(),
active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
created_at: Mapped[DateTime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
agreement_signed = db.Column(db.Boolean, nullable=True)
agreement_signed_at = db.Column(db.DateTime, nullable=True)
agreement_signed_by_id = db.Column(
UUID(as_uuid=True),
db.ForeignKey("users.id"),
nullable=True,
updated_at: Mapped[Optional[DateTime]] = mapped_column(
DateTime, nullable=True, onupdate=func.now()
)
agreement_signed_by = db.relationship("User")
agreement_signed_on_behalf_of_name = db.Column(db.String(255), nullable=True)
agreement_signed_on_behalf_of_email_address = db.Column(
db.String(255), nullable=True
agreement_signed: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True)
agreement_signed_at: Mapped[Optional[DateTime]] = mapped_column(
DateTime, nullable=True
)
agreement_signed_version = db.Column(db.Float, nullable=True)
organization_type = enum_column(OrganizationType, unique=False, nullable=True)
request_to_go_live_notes = db.Column(db.Text)

domains = db.relationship("Domain")

email_branding = db.relationship("EmailBranding")
email_branding_id = db.Column(
agreement_signed_by_id: Mapped[Optional[UUID]] = mapped_column(
UUID(as_uuid=True),
db.ForeignKey("email_branding.id"),
ForeignKey("users.id"),
nullable=True,
)
# Specify the relationship with explicity primaryjoin
agreement_signed_by = relationship(
"User",
back_populates="organizations",
primaryjoin="Organization.agreement_signed_by_id == User.id",
)
agreement_signed_on_behalf_of_name: Mapped[Optional[str]] = mapped_column(
String(255),
nullable=True,
)
agreement_signed_on_behalf_of_email_address: Mapped[Optional[str]] = mapped_column(
String(255),
nullable=True,
)
agreement_signed_version: Mapped[Optional[float]] = mapped_column(
Float, nullable=True
)
organization_type: Mapped[Optional[OrganizationType]] = mapped_column(
Enum(OrganizationType, name="organization_type_enum"), nullable=True
)
request_to_go_live_notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True)

notes = db.Column(db.Text, nullable=True)
purchase_order_number = db.Column(db.String(255), nullable=True)
billing_contact_names = db.Column(db.Text, nullable=True)
billing_contact_email_addresses = db.Column(db.Text, nullable=True)
billing_reference = db.Column(db.String(255), nullable=True)
domains: Mapped[List["Domain"]] = relationship("Domain", lazy="select")
email_branding: Mapped[Optional["Email_Branding"]] = relationship(
"Email_Branding",
primaryjoin="Organization.email_branding_id == Email_Branding.id",
)
email_branding_id: Mapped[Optional[UUID]] = mapped_column(
UUID(as_uuid=True), ForeignKey("email_branding.id"), nullable=True
)
notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
purchase_order_number: Mapped[Optional[str]] = mapped_column(
String(255), nullable=True
)
billing_contact_names: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
billing_contact_email_addresses: Mapped[Optional[str]] = mapped_column(
Text, nullable=True
)
billing_reference: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)

@property
def live_services(self):
def live_services(self) -> List["Service"]:
return [
service
for service in self.services
if service.active and not service.restricted
]

@property
def domain_list(self):
def domain_list(self) -> List[str]:
return [domain.domain for domain in self.domains]

@property
def agreement(self):
def agreement(self) -> Optional["Agreement"]:
try:
active_agreements = [
agreement
Expand All @@ -431,20 +477,20 @@ def agreement(self):
return None

@property
def agreement_active(self):
def agreement_active(self) -> bool:
try:
return self.agreement.status == AgreementStatus.ACTIVE
except AttributeError:
return False

@property
def has_mou(self):
def has_mou(self) -> bool:
try:
return self.agreement.type == AgreementType.MOU
except AttributeError:
return False

def serialize(self):
def serialize(self) -> dict:
return {
"id": str(self.id),
"name": self.name,
Expand All @@ -467,7 +513,7 @@ def serialize(self):
"billing_reference": self.billing_reference,
}

def serialize_for_list(self):
def serialize_for_list(self) -> dict:
return {
"name": self.name,
"id": str(self.id),
Expand Down Expand Up @@ -551,7 +597,7 @@ class Service(db.Model, Versioned):
billing_reference = db.Column(db.String(255), nullable=True)

email_branding = db.relationship(
"EmailBranding",
"Email_Branding",
secondary=service_email_branding,
uselist=False,
backref=db.backref("services", lazy="dynamic"),
Expand Down
Loading
Loading