From 3f77871c4fa8862103f8fc95b6cc802c1dc22c48 Mon Sep 17 00:00:00 2001 From: Josako Date: Mon, 9 Jun 2025 18:13:38 +0200 Subject: [PATCH] - Add a default make to the tenant - Add a make to the SpecialistMagicLink --- common/models/interaction.py | 3 +- common/models/user.py | 5 ++- eveai_app/views/interaction_forms.py | 20 +++++++++- eveai_app/views/interaction_views.py | 17 +++++++- eveai_app/views/user_forms.py | 9 +++++ eveai_app/views/user_views.py | 39 ++++++++++++++++++- ..._add_default_tenantmake_to_tenant_model.py | 35 +++++++++++++++++ ...ae6cc923e_add_tenant_make_reference_to_.py | 30 ++++++++++++++ 8 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 migrations/public/versions/83d4e90f87c6_add_default_tenantmake_to_tenant_model.py create mode 100644 migrations/tenant/versions/2b6ae6cc923e_add_tenant_make_reference_to_.py diff --git a/common/models/interaction.py b/common/models/interaction.py index 9c86af5..7b10e8f 100644 --- a/common/models/interaction.py +++ b/common/models/interaction.py @@ -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) diff --git a/common/models/user.py b/common/models/user.py index 321bb5b..32b16a0 100644 --- a/common/models/user.py +++ b/common/models/user.py @@ -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, } diff --git a/eveai_app/views/interaction_forms.py b/eveai_app/views/interaction_forms.py index 9c0320d..f153f31 100644 --- a/eveai_app/views/interaction_forms.py +++ b/eveai_app/views/interaction_forms.py @@ -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 + diff --git a/eveai_app/views/interaction_views.py b/eveai_app/views/interaction_views.py index 97507b4..64fc2cf 100644 --- a/eveai_app/views/interaction_views.py +++ b/eveai_app/views/interaction_views.py @@ -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)) diff --git a/eveai_app/views/user_forms.py b/eveai_app/views/user_forms.py index bdcb234..bc7bc47 100644 --- a/eveai_app/views/user_forms.py +++ b/eveai_app/views/user_forms.py @@ -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) diff --git a/eveai_app/views/user_views.py b/eveai_app/views/user_views.py index 3618659..52ab133 100644 --- a/eveai_app/views/user_views.py +++ b/eveai_app/views/user_views.py @@ -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) diff --git a/migrations/public/versions/83d4e90f87c6_add_default_tenantmake_to_tenant_model.py b/migrations/public/versions/83d4e90f87c6_add_default_tenantmake_to_tenant_model.py new file mode 100644 index 0000000..e653c0c --- /dev/null +++ b/migrations/public/versions/83d4e90f87c6_add_default_tenantmake_to_tenant_model.py @@ -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 ### diff --git a/migrations/tenant/versions/2b6ae6cc923e_add_tenant_make_reference_to_.py b/migrations/tenant/versions/2b6ae6cc923e_add_tenant_make_reference_to_.py new file mode 100644 index 0000000..3dae6f6 --- /dev/null +++ b/migrations/tenant/versions/2b6ae6cc923e_add_tenant_make_reference_to_.py @@ -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 ###