- Add a default make to the tenant

- Add a make to the SpecialistMagicLink
This commit is contained in:
Josako
2025-06-09 18:13:38 +02:00
parent 199cf94cf2
commit 3f77871c4f
8 changed files with 153 additions and 5 deletions

View File

@@ -1,7 +1,7 @@
from sqlalchemy.dialects.postgresql import JSONB
from ..extensions import db
from .user import User, Tenant
from .user import User, Tenant, TenantMake
from .document import Embedding, Retriever
@@ -223,6 +223,7 @@ class SpecialistMagicLink(db.Model):
name = db.Column(db.String(50), nullable=False)
description = db.Column(db.Text, nullable=True)
specialist_id = db.Column(db.Integer, db.ForeignKey(Specialist.id, ondelete='CASCADE'), nullable=False)
tenant_make_id = db.Column(db.Integer, db.ForeignKey(TenantMake.id, ondelete='CASCADE'), nullable=True)
magic_link_code = db.Column(db.String(55), nullable=False, unique=True)
valid_from = db.Column(db.DateTime, nullable=True)

View File

@@ -33,13 +33,15 @@ class Tenant(db.Model):
# Entitlements
currency = db.Column(db.String(20), nullable=True)
storage_dirty = db.Column(db.Boolean, nullable=True, default=False)
default_tenant_make_id = db.Column(db.Integer, db.ForeignKey('public.tenant_make.id'), nullable=True)
# Relations
users = db.relationship('User', backref='tenant')
domains = db.relationship('TenantDomain', backref='tenant')
licenses = db.relationship('License', back_populates='tenant')
license_usages = db.relationship('LicenseUsage', backref='tenant')
makes = db.relationship('TenantMake', backref='tenant')
tenant_makes = db.relationship('TenantMake', backref='tenant', foreign_keys='TenantMake.tenant_id')
default_tenant_make = db.relationship('TenantMake', foreign_keys=[default_tenant_make_id], uselist=False)
@property
def current_license(self):
@@ -63,6 +65,7 @@ class Tenant(db.Model):
'default_language': self.default_language,
'allowed_languages': self.allowed_languages,
'currency': self.currency,
'default_tenant_make_id': self.default_tenant_make_id,
}

View File

@@ -8,6 +8,7 @@ from wtforms_sqlalchemy.fields import QuerySelectMultipleField
from common.models.document import Retriever
from common.models.interaction import EveAITool, Specialist
from common.models.user import TenantMake
from common.extensions import cache_manager
from common.utils.form_assistants import validate_json
@@ -140,6 +141,7 @@ class SpecialistMagicLinkForm(FlaskForm):
description = TextAreaField('Description', validators=[Optional()])
magic_link_code = StringField('Magic Link Code', validators=[DataRequired(), Length(max=55)], render_kw={'readonly': True})
specialist_id = SelectField('Specialist', validators=[DataRequired()])
tenant_make_id = SelectField('Tenant Make', validators=[Optional()], coerce=int)
valid_from = DateField('Valid From', id='form-control datepicker', validators=[Optional()])
valid_to = DateField('Valid To', id='form-control datepicker', validators=[Optional()])
@@ -150,9 +152,13 @@ class SpecialistMagicLinkForm(FlaskForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
specialists = Specialist.query.all()
# Dynamically populate the 'type' field using the constructor
# Dynamically populate the specialist field
self.specialist_id.choices = [(specialist.id, specialist.name) for specialist in specialists]
# Dynamically populate the tenant_make field with None as first option
tenant_makes = TenantMake.query.all()
self.tenant_make_id.choices = [(0, 'None')] + [(make.id, make.name) for make in tenant_makes]
class EditSpecialistMagicLinkForm(DynamicFormBase):
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
@@ -161,6 +167,8 @@ class EditSpecialistMagicLinkForm(DynamicFormBase):
render_kw={'readonly': True})
specialist_id = IntegerField('Specialist', validators=[DataRequired()], render_kw={'readonly': True})
specialist_name = StringField('Specialist Name', validators=[DataRequired()], render_kw={'readonly': True})
tenant_make_id = SelectField('Tenant Make', validators=[Optional()], coerce=int)
tenant_make_name = StringField('Tenant Make Name', validators=[Optional()], render_kw={'readonly': True})
valid_from = DateField('Valid From', id='form-control datepicker', validators=[Optional()])
valid_to = DateField('Valid To', id='form-control datepicker', validators=[Optional()])
@@ -176,5 +184,15 @@ class EditSpecialistMagicLinkForm(DynamicFormBase):
else:
self.specialist_name.data = ''
# Dynamically populate the tenant_make field with None as first option
tenant_makes = TenantMake.query.all()
self.tenant_make_id.choices = [(0, 'None')] + [(make.id, make.name) for make in tenant_makes]
# If the form has a tenant_make_id that's not zero, set the tenant_make_name
if hasattr(self, 'tenant_make_id') and self.tenant_make_id.data and self.tenant_make_id.data > 0:
tenant_make = TenantMake.query.get(self.tenant_make_id.data)
if tenant_make:
self.tenant_make_name.data = tenant_make.name

View File

@@ -691,9 +691,13 @@ def specialist_magic_link():
try:
new_specialist_magic_link = SpecialistMagicLink()
# Populate fields individually instead of using populate_obj (gives problem with QueryMultipleSelectField)
# Populate fields individually instead of using populate_obj
form.populate_obj(new_specialist_magic_link)
# Handle the tenant_make_id special case (0 = None)
if form.tenant_make_id.data == 0:
new_specialist_magic_link.tenant_make_id = None
set_logging_information(new_specialist_magic_link, dt.now(tz.utc))
# Create 'public' SpecialistMagicLinkTenant
@@ -733,12 +737,23 @@ def edit_specialist_magic_link(specialist_magic_link_id):
form.add_dynamic_fields("arguments", specialist_config, specialist_ml.specialist_args)
# Set the tenant_make_id default value
if request.method == 'GET':
if specialist_ml.tenant_make_id is None:
form.tenant_make_id.data = 0
else:
form.tenant_make_id.data = specialist_ml.tenant_make_id
if form.validate_on_submit():
# Update the basic fields
form.populate_obj(specialist_ml)
# Update the arguments dynamic fields
specialist_ml.specialist_args = form.get_dynamic_data("arguments")
# Handle the tenant_make_id special case (0 = None)
if form.tenant_make_id.data == 0:
specialist_ml.tenant_make_id = None
# Update logging information
update_logging_information(specialist_ml, dt.now(tz.utc))

View File

@@ -25,6 +25,8 @@ class TenantForm(FlaskForm):
currency = SelectField('Currency', choices=[], validators=[DataRequired()])
# Timezone
timezone = SelectField('Timezone', choices=[], validators=[DataRequired()])
# Default tenant make
default_tenant_make_id = SelectField('Default Tenant Make', choices=[], validators=[Optional()])
# For Super Users only - Allow to assign the tenant to the partner
assign_to_partner = BooleanField('Assign to Partner', default=False)
@@ -42,6 +44,13 @@ class TenantForm(FlaskForm):
self.timezone.choices = [(tz, tz) for tz in pytz.common_timezones]
# Initialize fallback algorithms
self.type.choices = [(t, t) for t in current_app.config['TENANT_TYPES']]
# Initialize default tenant make choices
tenant_id = session.get('tenant', {}).get('id') if 'tenant' in session else None
if tenant_id:
tenant_makes = TenantMake.query.filter_by(tenant_id=tenant_id, active=True).all()
self.default_tenant_make_id.choices = [(str(make.id), make.name) for make in tenant_makes]
# Add empty choice
self.default_tenant_make_id.choices.insert(0, ('', 'Geen'))
# Show field only for Super Users with partner in session
if not current_user.has_roles('Super User') or 'partner' not in session:
self._fields.pop('assign_to_partner', None)

View File

@@ -53,6 +53,10 @@ def tenant():
new_tenant = Tenant()
form.populate_obj(new_tenant)
# Convert default_tenant_make_id to integer if not empty
if form.default_tenant_make_id.data:
new_tenant.default_tenant_make_id = int(form.default_tenant_make_id.data)
timestamp = dt.now(tz.utc)
new_tenant.created_at = timestamp
new_tenant.updated_at = timestamp
@@ -118,6 +122,12 @@ def edit_tenant(tenant_id):
# Populate the tenant with form data
form.populate_obj(tenant)
# Convert default_tenant_make_id to integer if not empty
if form.default_tenant_make_id.data:
tenant.default_tenant_make_id = int(form.default_tenant_make_id.data)
else:
tenant.default_tenant_make_id = None
db.session.commit()
flash('Tenant updated successfully.', 'success')
if session.get('tenant'):
@@ -462,7 +472,17 @@ def tenant_overview():
tenant_id = session['tenant']['id']
tenant = Tenant.query.get_or_404(tenant_id)
form = TenantForm(obj=tenant)
return render_template('user/tenant_overview.html', form=form)
# Zet de waarde van default_tenant_make_id
if tenant.default_tenant_make_id:
form.default_tenant_make_id.data = str(tenant.default_tenant_make_id)
# Haal de naam van de default make op als deze bestaat
default_make_name = None
if tenant.default_tenant_make:
default_make_name = tenant.default_tenant_make.name
return render_template('user/tenant_overview.html', form=form, default_make_name=default_make_name)
@user_bp.route('/tenant_project', methods=['GET', 'POST'])
@@ -727,6 +747,23 @@ def handle_tenant_make_selection():
if action == 'edit_tenant_make':
return redirect(prefixed_url_for('user_bp.edit_tenant_make', tenant_make_id=tenant_make_id))
elif action == 'set_as_default':
# Set this make as the default for the tenant
tenant_id = session['tenant']['id']
tenant = Tenant.query.get(tenant_id)
tenant.default_tenant_make_id = tenant_make_id
try:
db.session.commit()
flash(f'Default tenant make updated successfully.', 'success')
# Update session data if necessary
if 'tenant' in session:
session['tenant'] = tenant.to_dict()
except SQLAlchemyError as e:
db.session.rollback()
flash(f'Failed to update default tenant make. Error: {str(e)}', 'danger')
current_app.logger.error(f'Failed to update default tenant make. Error: {str(e)}')
return redirect(prefixed_url_for('user_bp.tenant_makes'))
def reset_uniquifier(user):
security.datastore.set_uniquifier(user)

View File

@@ -0,0 +1,35 @@
"""Add default TenantMake to Tenant model
Revision ID: 83d4e90f87c6
Revises: f40d16a0965a
Create Date: 2025-06-09 15:42:51.503696
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '83d4e90f87c6'
down_revision = 'f40d16a0965a'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('tenant', schema=None) as batch_op:
batch_op.add_column(sa.Column('default_tenant_make_id', sa.Integer(), nullable=True))
batch_op.create_foreign_key(None, 'tenant_make', ['default_tenant_make_id'], ['id'], referent_schema='public')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('tenant', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.drop_column('default_tenant_make_id')
# ### end Alembic commands ###

View File

@@ -0,0 +1,30 @@
"""Add tenant_make reference to SpecialistMagicLink
Revision ID: 2b6ae6cc923e
Revises: a179785e5362
Create Date: 2025-06-09 15:59:39.157066
"""
from alembic import op
import sqlalchemy as sa
import pgvector
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '2b6ae6cc923e'
down_revision = 'a179785e5362'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('specialist_magic_link', sa.Column('tenant_make_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'specialist_magic_link', 'tenant_make', ['tenant_make_id'], ['id'], referent_schema='public', ondelete='CASCADE')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###