Feat(Project Hub): Apply for Founding Partner Program page created with database structure and methods.

This commit is contained in:
2025-08-02 17:39:22 +01:00
parent 09af0a7a93
commit 438909b102
343 changed files with 8047 additions and 4253 deletions

View File

@@ -33,15 +33,19 @@ class Form_Base(FlaskForm, metaclass=Form_Base_Meta):
def get_default(cls):
return cls()
@classmethod
def get_select_option_blank(cls):
return (cls.get_select_option_default_value(), 'Select')
def get_select_option_blank(cls, is_valid = True):
value = cls.get_select_valid_option_default_value() if is_valid else cls.get_select_invalid_option_default_value()
return (value, 'Select')
@classmethod
def get_select_option_all(cls):
return (cls.get_select_option_default_value(), 'All')
return (cls.get_select_valid_option_default_value(), 'All')
@staticmethod
def get_select_option_default_value():
def get_select_valid_option_default_value():
return '0'
@staticmethod
def get_select_invalid_option_default_value():
return ''
def __repr__(self):
fields = ', '.join(

View File

@@ -50,78 +50,78 @@ class Filters_Assessment(Form_Base):
id_assessment = SelectField(
'Assessment'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_weather = SelectField(
'Weather'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_lighting_level = SelectField(
'Lighting Level'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_location = SelectField(
'Location'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_user_handler = SelectField(
'Handler'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
"""
id_distraction_type = SelectField(
'Distraction Type'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_intensity_level_emotional = SelectField(
'Intensity Level Emotional'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_intensity_level_scent = SelectField(
'Intensity Level Scent'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_intensity_level_sight = SelectField(
'Intensity Level Sight'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_intensity_level_sound = SelectField(
'Intensity Level Sound'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_intensity_level_touch = SelectField(
'Intensity Level Touch'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_command_category = SelectField(
'Command Category'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_command = SelectField(
'Command'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_command_modality = SelectField(
'Command Modality'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_bribe = SelectField(
'Bribe'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
"""
active_only = BooleanField(

View File

@@ -37,7 +37,7 @@ class Filters_Calendar_Entry(Form_Base):
id_calendar_entry_type = SelectField(
'Type'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
active_only = BooleanField(
'Active'

View File

@@ -37,7 +37,7 @@ class Filters_Command(Form_Base):
id_command_category = SelectField(
'Command Category'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
active_only = BooleanField(
'Active'

View File

@@ -43,32 +43,32 @@ class Filters_Command_Button_Link(Form_Base):
id_command_category = SelectField(
'Command Category'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_command = SelectField(
'Command'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_button_shape = SelectField(
'Shape'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_colour = SelectField(
'Colour'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_button_icon = SelectField(
'Icon'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_location = SelectField(
'Location'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
active_only = BooleanField(
'Active'

View File

@@ -39,17 +39,17 @@ class Filters_Dog_Command_Link(Form_Base):
id_dog = SelectField(
'Dog'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_command_category = SelectField(
'Command Category'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
id_command = SelectField(
'Command'
, choices = [Form_Base.get_select_option_all()]
, default = Form_Base.get_select_option_default_value()
, default = Form_Base.get_select_valid_option_default_value()
)
active_only = BooleanField(
'Active'

View File

@@ -0,0 +1,171 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: Apply to Founding Partner Program Form
Description:
Defines Flask-WTF form for handling user input on Apply to Founding Partner Program page.
"""
# IMPORTS
# internal
from business_objects.base import Base
from business_objects.project_hub.apply_founding_partner_form import Apply_Founding_Partner_Form
from business_objects.project_hub.contact_form import Contact_Form
from forms.project_hub.contact import AltchaValidator, AltchaField
# from models.model_view_store import Model_View_Store # circular
from models.model_view_base import Model_View_Base
from forms.base import Form_Base
# external
from flask import Flask, render_template, request, flash, redirect, url_for, current_app
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField, HiddenField, BooleanField, Field, EmailField, IntegerField, SelectField, SelectMultipleField
from wtforms.validators import DataRequired, Email, ValidationError, Optional
from wtforms.widgets import CheckboxInput, ListWidget
import markupsafe
from flask_wtf.recaptcha import RecaptchaField
from abc import ABCMeta, abstractmethod
import json
from altcha import verify_solution
import base64
class MultiCheckboxField(SelectMultipleField):
widget = ListWidget(prefix_label=False)
option_widget = CheckboxInput()
class Form_Apply_Founding_Partner(FlaskForm):
contact_name = StringField(
'Name'
, validators = [DataRequired()]
)
email = EmailField(
'Email'
, validators = [DataRequired()]
)
phone_number = StringField(
'Phone number'
, validators = [Optional()]
)
company_name = StringField(
'Company'
, validators = [DataRequired()]
)
website = StringField(
'Website / social media'
, validators = [Optional()]
)
dog_count = IntegerField(
'How many dogs do you currently train regularly?'
, validators = [DataRequired()]
)
id_years_of_experience = SelectField(
'How long have you been training professionally?'
, choices = [
( '1', 'Less than 1 year' )
, ( '2', 'Less than 3 years' )
, ( '3', 'Less than 5 years' )
, ( '4', 'More than 5 years' )
]
, validators = [DataRequired()]
)
id_speciality = MultiCheckboxField( # SelectMultipleField(
'What type(s) of training do you specialise in?'
, choices = [
( '1', 'Basic obedience' )
, ( '2', 'Puppy training' )
, ( '3', 'Behaviour modification' )
, ( '4', 'Advanced / competition training' )
, ( '5', 'Service / therapy dog training' )
, ( '6', 'Group classes' )
, ( '7', 'One-on-one sessions' )
, ( '8', 'Other (specify below)' )
]
, validators = [DataRequired()]
)
id_existing_system = MultiCheckboxField( # SelectMultipleField(
'How do you currently manage client information and training records?'
, choices = [
( '1', 'Spreadsheets (Excel/Google Sheets)' )
, ( '2', 'Paper notes/binders' )
, ( '3', 'Generic CRM software' )
, ( '4', 'Mix of different apps' )
, ( '5', 'Mostly in my head/memory' )
, ( '6', 'Other (specify below)' )
]
, validators = [DataRequired()]
)
existing_challenges = TextAreaField(
"What's your biggest frustration with your current system?"
, validators = [DataRequired()]
)
id_existing_time_sink_weekly = SelectField(
'How much time do you spend on training-related admin work per week?'
, choices = [
( '1', 'Less than 1 hour' )
, ( '2', 'Less than 3 hours' )
, ( '3', 'Less than 6 hours' )
, ( '3', 'Less than 10 hours' )
, ( '4', 'More than 10 hours' )
]
, validators = [DataRequired()]
)
id_commitment_frequency = SelectField(
'Are you willing to provide regular feedback during the beta phase?'
, choices = [
( '1', 'Yes, weekly feedback' )
, ( '2', 'Yes, monthly feedback' )
, ( '3', 'Occasional feedback only' )
, ( '4', 'Just want to use the software' )
]
, validators = [DataRequired()]
)
"""
implementation_timeline = SelectField(
"What's your timeline for implementing new business management software?"
, choices = [
, ( '1', 'Immediately' )
, ( '2', 'Within 1 month' )
, ( '3', 'Within 3 months' )
, ( '4', 'When the right solution comes along' )
, ( '5', 'No timeline yet' )
]
, validators = [DataRequired()]
)
most_valuable_feature = TextAreaField(
'What feature would be most valuable to your business?'
, validators = [DataRequired()]
)
"""
notes = TextAreaField(
"Anything else you'd like us to know about your business or training approach?"
, validators = [Optional()]
)
altcha = AltchaField('Verify you are human')
submit = SubmitField('Send Message')
def to_json(self):
return {
Contact_Form.FLAG_NAME_CONTACT: self.contact_name.data
, Base.FLAG_EMAIL: self.email.data
, Base.FLAG_PHONE_NUMBER: self.phone_number.data
, Contact_Form.FLAG_NAME_COMPANY: self.company_name.data
, Base.FLAG_WEBSITE: self.website.data
, Apply_Founding_Partner_Form.FLAG_DOG_COUNT: self.dog_count.data
, Apply_Founding_Partner_Form.FLAG_YEARS_OF_EXPERIENCE: self.id_years_of_experience.data
, Apply_Founding_Partner_Form.FLAG_SPECIALITY: self.id_speciality.data
, Apply_Founding_Partner_Form.FLAG_EXISTING_SYSTEM: self.id_existing_system.data
, Apply_Founding_Partner_Form.FLAG_EXISTING_CHALLENGES: self.existing_challenges.data
, Apply_Founding_Partner_Form.FLAG_EXISTING_TIME_SINK_WEEKLY: self.id_existing_time_sink_weekly.data
, Apply_Founding_Partner_Form.FLAG_COMMITMENT_FREQUENCY: self.id_commitment_frequency.data
# , Apply_Founding_Partner_Form.FLAG_IMPLEMENTATION_TIMELINE: self.implementation_timeline.data
# , Apply_Founding_Partner_Form.FLAG_MOST_VALUABLE_FEATURE: self.most_valuable_feature.data
, Apply_Founding_Partner_Form.FLAG_NOTES: self.notes.data
, Contact_Form.FLAG_ALTCHA: self.altcha.data
, Base.FLAG_ACTIVE: True
, Base.FLAG_CREATED_ON: None
}

View File

@@ -29,13 +29,15 @@ import json
from altcha import verify_solution
import base64
class ALTCHAValidator:
class AltchaValidator:
def __init__(self, message=None):
self.message = message or 'ALTCHA verification failed'
def __call__(self, form, field):
altcha_data = field.data
return True
if not altcha_data:
raise ValidationError(self.message)
@@ -57,12 +59,12 @@ class ALTCHAValidator:
except Exception as e:
raise ValidationError(f'Invalid ALTCHA data: {str(e)}')
class ALTCHAField(Field):
class AltchaField(Field):
def __init__(self, label='', validators=None, **kwargs):
validators = validators or []
validators.append(ALTCHAValidator())
validators.append(AltchaValidator())
super(ALTCHAField, self).__init__(label, validators, **kwargs)
super(AltchaField, self).__init__(label, validators, **kwargs)
def __call__(self, **kwargs):
html = f"""
@@ -84,7 +86,7 @@ class Form_Contact(FlaskForm):
receive_marketing = BooleanField('I would like to receive marketing emails.')
# recaptcha = RecaptchaField()
# altcha = HiddenField('ALTCHA') # , validators=[validate_altcha]
altcha = ALTCHAField('Verify you are human')
altcha = AltchaField('Verify you are human')
submit = SubmitField('Send Message')
def to_json(self):