generated from dopt-python/py311
Dev #7
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import zoneinfo
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
@@ -25,3 +26,6 @@ class Config:
|
||||
PATH_LOGGING: Path = BASE_PATH / os.getenv("DOPT_PATH_LOGGING", "data/d-opt.log")
|
||||
LOG_FILENAME: str = "dopt.log"
|
||||
ALEMBIC_PATH: Path = BASE_PATH / os.getenv("DOPT_ALEMBIC_BASE", "python/alembic")
|
||||
|
||||
|
||||
TIMEZONE_CEST: Final[zoneinfo.ZoneInfo] = zoneinfo.ZoneInfo("Europe/Berlin")
|
||||
|
||||
@@ -16,7 +16,9 @@ class FormFieldType(enum.StrEnum):
|
||||
TEXT = enum.auto()
|
||||
LONGTEXT = enum.auto()
|
||||
DATE = enum.auto()
|
||||
DATE_FLEXIBLE = enum.auto()
|
||||
DATETIME = enum.auto()
|
||||
DATETIME_FLEXIBLE = enum.auto()
|
||||
DROPDOWN = enum.auto()
|
||||
EXTENDED_DROPDOWN = enum.auto()
|
||||
DYNAMIC_LIST = enum.auto()
|
||||
|
||||
+455
-6
@@ -15,14 +15,17 @@ from pprint import pformat
|
||||
from typing import TYPE_CHECKING, Any, Final, Protocol, TypeAlias, TypedDict, TypeVar, cast
|
||||
from typing_extensions import override
|
||||
|
||||
from dopt_basics import datetime as dopt_dt
|
||||
from pydantic import (
|
||||
ValidationError,
|
||||
)
|
||||
from PySide6.QtCore import (
|
||||
QDate,
|
||||
QDateTime,
|
||||
QEvent,
|
||||
QObject,
|
||||
Qt,
|
||||
QTime,
|
||||
QTimer,
|
||||
QtMsgType,
|
||||
Signal,
|
||||
@@ -33,6 +36,7 @@ from PySide6.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QApplication,
|
||||
QButtonGroup,
|
||||
QCalendarWidget,
|
||||
QComboBox,
|
||||
QCompleter,
|
||||
QDateEdit,
|
||||
@@ -61,6 +65,7 @@ from PySide6.QtWidgets import (
|
||||
|
||||
import wce_crm.constants
|
||||
from wce_crm.backend import backend
|
||||
from wce_crm.constants import TIMEZONE_CEST
|
||||
from wce_crm.data_models import (
|
||||
COLUMN_SEP,
|
||||
FlatBaseModel,
|
||||
@@ -406,6 +411,54 @@ def _build_ui_recursively(
|
||||
else:
|
||||
parent_layout.addRow(field.label, widget)
|
||||
|
||||
case FormFieldType.DATE_FLEXIBLE:
|
||||
widget = FlexibleDateInput()
|
||||
if field.fill_value:
|
||||
set_date = QDate.fromString(field.fill_value, "dd.MM.yyyy")
|
||||
if not set_date.isValid():
|
||||
raise ValueError(
|
||||
f"Could not parse date field value >{field.fill_value}<"
|
||||
)
|
||||
widget.set_date(set_date)
|
||||
|
||||
if field.readonly:
|
||||
widget.setReadOnly(True)
|
||||
|
||||
widget_registry[full_key] = {
|
||||
"widget": widget,
|
||||
"form_field": field,
|
||||
}
|
||||
|
||||
if field.tooltip:
|
||||
tooltip_layout = _add_tooltip(widget, field.tooltip)
|
||||
parent_layout.addRow(field.label, tooltip_layout)
|
||||
else:
|
||||
parent_layout.addRow(field.label, widget)
|
||||
|
||||
case FormFieldType.DATETIME_FLEXIBLE:
|
||||
widget = FlexibleDateTimeInput()
|
||||
if field.fill_value:
|
||||
set_datetime = QDateTime.fromString(field.fill_value, "dd.MM.yyyy hh:mm")
|
||||
if not set_datetime.isValid():
|
||||
raise ValueError(
|
||||
f"Could not parse date field value >{field.fill_value}<"
|
||||
)
|
||||
widget.set_datetime(set_datetime)
|
||||
|
||||
if field.readonly:
|
||||
widget.setReadOnly(True)
|
||||
|
||||
widget_registry[full_key] = {
|
||||
"widget": widget,
|
||||
"form_field": field,
|
||||
}
|
||||
|
||||
if field.tooltip:
|
||||
tooltip_layout = _add_tooltip(widget, field.tooltip)
|
||||
parent_layout.addRow(field.label, tooltip_layout)
|
||||
else:
|
||||
parent_layout.addRow(field.label, widget)
|
||||
|
||||
case FormFieldType.DROPDOWN:
|
||||
widget = QComboBox()
|
||||
widget.addItem(DROPDOWN_DEFAULT, None)
|
||||
@@ -576,6 +629,24 @@ def reset_form(
|
||||
widget.setDate(set_date)
|
||||
else:
|
||||
widget.setDate(QDate.currentDate())
|
||||
elif isinstance(widget, FlexibleDateInput):
|
||||
widget.reset()
|
||||
if form_field.fill_value:
|
||||
set_date = QDate.fromString(form_field.fill_value, "dd.MM.yyyy")
|
||||
if not set_date.isValid():
|
||||
raise ValueError(
|
||||
f"Could not parse date field value >{form_field.fill_value}<"
|
||||
)
|
||||
widget.set_date(set_date)
|
||||
elif isinstance(widget, FlexibleDateTimeInput):
|
||||
widget.reset()
|
||||
if form_field.fill_value:
|
||||
set_datetime = QDateTime.fromString(form_field.fill_value, "dd.MM.yyyy hh:mm")
|
||||
if not set_datetime.isValid():
|
||||
raise ValueError(
|
||||
f"Could not parse date field value >{form_field.fill_value}<"
|
||||
)
|
||||
widget.set_datetime(set_datetime)
|
||||
elif isinstance(widget, QComboBox):
|
||||
if form_field.fill_value:
|
||||
widget.setCurrentText(form_field.fill_value)
|
||||
@@ -699,6 +770,10 @@ def get_form_data(
|
||||
elif isinstance(widget, QDateEdit):
|
||||
qt_date = widget.date()
|
||||
value = qt_date.toPython()
|
||||
elif isinstance(widget, FlexibleDateInput):
|
||||
value = widget.get_pydate()
|
||||
elif isinstance(widget, FlexibleDateTimeInput):
|
||||
value = widget.get_pydatetime()
|
||||
elif isinstance(widget, QComboBox):
|
||||
value = widget.currentData()
|
||||
elif isinstance(
|
||||
@@ -754,7 +829,18 @@ def set_widget_value(
|
||||
if not set_date.isValid():
|
||||
raise ValueError(f"Could not parse date field value >{value}<")
|
||||
widget.setDate(set_date)
|
||||
|
||||
elif isinstance(widget, FlexibleDateInput):
|
||||
assert isinstance(value, datetime.datetime)
|
||||
try:
|
||||
widget.set_pydate(value, raise_error=True)
|
||||
except ValueError:
|
||||
raise ValueError(f"Could not parse date field value >{value}<")
|
||||
elif isinstance(widget, FlexibleDateTimeInput):
|
||||
assert isinstance(value, datetime.datetime)
|
||||
try:
|
||||
widget.set_pydatetime(value, raise_error=True)
|
||||
except ValueError:
|
||||
raise ValueError(f"Could not parse datetime field value >{value}<")
|
||||
elif isinstance(widget, QComboBox):
|
||||
index = widget.findData(value)
|
||||
if index >= 0:
|
||||
@@ -847,6 +933,16 @@ def validate_form_data(
|
||||
if widget.currentData() is not None:
|
||||
continue
|
||||
error_post = True
|
||||
elif isinstance(widget, FlexibleDateInput):
|
||||
date = widget.get_date()
|
||||
if date and date.isValid():
|
||||
continue
|
||||
error_post = True
|
||||
elif isinstance(widget, FlexibleDateTimeInput):
|
||||
date = widget.get_datetime()
|
||||
if date and date.isValid():
|
||||
continue
|
||||
error_post = True
|
||||
elif isinstance(
|
||||
widget,
|
||||
(
|
||||
@@ -1477,6 +1573,355 @@ class AutoForm(QWidget):
|
||||
set_form_data(self.widget_registry, data)
|
||||
|
||||
|
||||
class FlexibleDateInput(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._read_only = False
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# simple text field allows for easy editing and deletion
|
||||
self.line_edit = QLineEdit()
|
||||
self.line_edit.setPlaceholderText("TT.MM.JJJJ")
|
||||
self.line_edit.setInputMask("99.99.9999;_") # force date structure
|
||||
|
||||
self.button = QPushButton("📅")
|
||||
self.button.setFixedWidth(30)
|
||||
|
||||
layout.addWidget(self.line_edit)
|
||||
layout.addWidget(self.button)
|
||||
|
||||
self.calendar = QCalendarWidget()
|
||||
self.calendar.setWindowFlags(Qt.WindowType.Popup) # calendar as free-floating window
|
||||
self.calendar.setFirstDayOfWeek(Qt.DayOfWeek.Monday)
|
||||
self.calendar.setGridVisible(True)
|
||||
self.calendar.setStyleSheet("""
|
||||
QCalendarWidget QAbstractItemView {
|
||||
selection-background-color: #2980b9;
|
||||
selection-color: white;
|
||||
}
|
||||
""")
|
||||
|
||||
self.button.clicked.connect(self.show_calendar)
|
||||
self.calendar.activated.connect(self.set_date)
|
||||
# signal to validate
|
||||
self.line_edit.editingFinished.connect(self.validate_input)
|
||||
self.line_edit.installEventFilter(self)
|
||||
|
||||
self.setReadOnly(False)
|
||||
|
||||
def eventFilter(
|
||||
self,
|
||||
watched: QObject,
|
||||
event: QEvent,
|
||||
):
|
||||
# press enter: direct validation
|
||||
if watched == self.line_edit:
|
||||
if event.type() == QEvent.Type.KeyPress:
|
||||
if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter): # type: ignore
|
||||
self.validate_input()
|
||||
return True # mark event as processed
|
||||
elif event.type() == QEvent.Type.FocusOut:
|
||||
self.validate_input()
|
||||
# no return to not block focus change
|
||||
return super().eventFilter(watched, event)
|
||||
|
||||
def show_calendar(self) -> None:
|
||||
if self._read_only:
|
||||
return
|
||||
pos = self.line_edit.mapToGlobal(self.line_edit.rect().bottomLeft())
|
||||
self.calendar.move(pos)
|
||||
self.calendar.show()
|
||||
|
||||
def isReadOnly(self) -> bool:
|
||||
return self._read_only
|
||||
|
||||
def setReadOnly(
|
||||
self,
|
||||
read_only: bool,
|
||||
):
|
||||
self._read_only = read_only
|
||||
self.line_edit.setReadOnly(read_only)
|
||||
self.button.setEnabled(not read_only)
|
||||
|
||||
current_text = self.line_edit.text()
|
||||
|
||||
if read_only:
|
||||
self.line_edit.setInputMask("")
|
||||
self.line_edit.setPlaceholderText("kein Datum eingetragen")
|
||||
pure = (
|
||||
current_text.replace(".", "")
|
||||
.replace(":", "")
|
||||
.replace(" ", "")
|
||||
.replace("_", "")
|
||||
.strip()
|
||||
)
|
||||
cleaned_date = current_text if pure else ""
|
||||
self.line_edit.setText(cleaned_date)
|
||||
self.line_edit.setProperty("styleClass", "stempel")
|
||||
else:
|
||||
self.line_edit.setInputMask("99.99.9999;_")
|
||||
self.line_edit.setPlaceholderText("TT.MM.JJJJ")
|
||||
|
||||
def validate_input(self) -> None:
|
||||
logger_gui.debug("[Widget -- FlexibleDateInput] Trigger input validation")
|
||||
if self._read_only:
|
||||
return
|
||||
|
||||
focused_widget = QApplication.focusWidget()
|
||||
if focused_widget == self.button or focused_widget == self.calendar:
|
||||
return
|
||||
|
||||
text = self.line_edit.text()
|
||||
# field empty
|
||||
if not text.replace(".", "").replace("_", "").strip():
|
||||
self.line_edit.setStyleSheet("")
|
||||
return
|
||||
|
||||
# if user wrote input
|
||||
date = QDate.fromString(text, "dd.MM.yyyy")
|
||||
|
||||
if not date.isValid():
|
||||
self.line_edit.setStyleSheet("border: 1px solid red; background-color: #FFEEEE;")
|
||||
self.line_edit.selectAll()
|
||||
# optional: keep focus in field to directly correct entry
|
||||
# self.line_edit.setFocus()
|
||||
else:
|
||||
self.line_edit.setStyleSheet("")
|
||||
|
||||
def set_date(
|
||||
self,
|
||||
date: QDate,
|
||||
) -> None:
|
||||
if self._read_only:
|
||||
return
|
||||
|
||||
if date and date.isValid():
|
||||
self.line_edit.setText(date.toString("dd.MM.yyyy"))
|
||||
else:
|
||||
self.line_edit.clear()
|
||||
|
||||
self.line_edit.setText(date.toString("dd.MM.yyyy"))
|
||||
self.calendar.hide()
|
||||
|
||||
def set_pydate(
|
||||
self,
|
||||
date: datetime.datetime,
|
||||
raise_error: bool = False,
|
||||
) -> None:
|
||||
date_to_set = QDate(date.year, date.month, date.day)
|
||||
if raise_error and not date_to_set.isValid():
|
||||
raise ValueError("Could not parse date object correctly")
|
||||
|
||||
self.set_date(date_to_set)
|
||||
|
||||
def get_date(self) -> QDate:
|
||||
# valid or invalid QDate
|
||||
return QDate.fromString(self.line_edit.text(), "dd.MM.yyyy")
|
||||
|
||||
def get_pydate(self) -> datetime.date:
|
||||
# valid or invalid QDate
|
||||
date = self.get_date()
|
||||
py_date = cast(datetime.date, date.toPython())
|
||||
|
||||
return py_date
|
||||
|
||||
def reset(self) -> None:
|
||||
self.calendar.hide()
|
||||
self.line_edit.clear()
|
||||
|
||||
|
||||
class FlexibleDateTimeInput(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._read_only = False
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Simple text field allows for easy editing and deletion (with Time)
|
||||
self.line_edit = QLineEdit()
|
||||
self.line_edit.setPlaceholderText("TT.MM.JJJJ hh:mm")
|
||||
self.line_edit.setInputMask("99.99.9999 99:99;_") # force datetime structure
|
||||
|
||||
self.button = QPushButton("📅")
|
||||
self.button.setFixedWidth(30)
|
||||
|
||||
layout.addWidget(self.line_edit)
|
||||
layout.addWidget(self.button)
|
||||
|
||||
self.calendar = QCalendarWidget()
|
||||
self.calendar.setWindowFlags(Qt.WindowType.Popup) # calendar as free-floating window
|
||||
self.calendar.setFirstDayOfWeek(Qt.DayOfWeek.Monday)
|
||||
self.calendar.setGridVisible(True)
|
||||
self.calendar.setStyleSheet("""
|
||||
QCalendarWidget QAbstractItemView {
|
||||
selection-background-color: #2980b9;
|
||||
selection-color: white;
|
||||
}
|
||||
""")
|
||||
|
||||
self.button.clicked.connect(self.show_calendar)
|
||||
self.calendar.activated.connect(self.set_date_from_calendar)
|
||||
|
||||
# signal to validate
|
||||
self.line_edit.editingFinished.connect(self.validate_input)
|
||||
self.line_edit.installEventFilter(self)
|
||||
|
||||
self.setReadOnly(False)
|
||||
|
||||
def eventFilter(
|
||||
self,
|
||||
watched: QObject,
|
||||
event: QEvent,
|
||||
):
|
||||
# press enter: direct validation
|
||||
if watched == self.line_edit:
|
||||
if event.type() == QEvent.Type.KeyPress:
|
||||
if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter): # type: ignore
|
||||
self.validate_input()
|
||||
return True # mark event as processed
|
||||
elif event.type() == QEvent.Type.FocusOut:
|
||||
self.validate_input()
|
||||
# no return to not block focus change
|
||||
return super().eventFilter(watched, event)
|
||||
|
||||
def show_calendar(self) -> None:
|
||||
if self._read_only:
|
||||
return
|
||||
pos = self.line_edit.mapToGlobal(self.line_edit.rect().bottomLeft())
|
||||
self.calendar.move(pos)
|
||||
self.calendar.show()
|
||||
|
||||
def isReadOnly(self) -> bool:
|
||||
return self._read_only
|
||||
|
||||
def setReadOnly(
|
||||
self,
|
||||
read_only: bool,
|
||||
):
|
||||
self._read_only = read_only
|
||||
self.line_edit.setReadOnly(read_only)
|
||||
self.button.setEnabled(not read_only)
|
||||
|
||||
current_text = self.line_edit.text()
|
||||
|
||||
if read_only:
|
||||
self.line_edit.setInputMask("")
|
||||
self.line_edit.setPlaceholderText("kein Zeitstempel eingetragen")
|
||||
pure = (
|
||||
current_text.replace(".", "")
|
||||
.replace(":", "")
|
||||
.replace(" ", "")
|
||||
.replace("_", "")
|
||||
.strip()
|
||||
)
|
||||
cleaned_datetime = current_text if pure else ""
|
||||
self.line_edit.setText(cleaned_datetime)
|
||||
self.line_edit.setProperty("styleClass", "stempel")
|
||||
else:
|
||||
self.line_edit.setInputMask("99.99.9999 99:99;_")
|
||||
self.line_edit.setPlaceholderText("TT.MM.JJJJ hh:mm")
|
||||
|
||||
def validate_input(self) -> None:
|
||||
logger_gui.debug("[Widget -- FlexibleDateTimeInput] Trigger input validation")
|
||||
if self._read_only:
|
||||
return
|
||||
|
||||
focused_widget = QApplication.focusWidget()
|
||||
if focused_widget == self.button or focused_widget == self.calendar:
|
||||
return
|
||||
|
||||
text = self.line_edit.text()
|
||||
# field empty (ignoring dots, colons, spaces, underscores)
|
||||
pure_text = (
|
||||
text.replace(".", "").replace(":", "").replace(" ", "").replace("_", "").strip()
|
||||
)
|
||||
if not pure_text:
|
||||
self.line_edit.setStyleSheet("")
|
||||
return
|
||||
|
||||
# check logic for QDateTime
|
||||
dt = QDateTime.fromString(text, "dd.MM.yyyy hh:mm")
|
||||
|
||||
if not dt.isValid():
|
||||
self.line_edit.setStyleSheet("border: 1px solid red; background-color: #FFEEEE;")
|
||||
self.line_edit.selectAll()
|
||||
else:
|
||||
self.line_edit.setStyleSheet("")
|
||||
|
||||
def set_date_from_calendar(
|
||||
self,
|
||||
date: QDate,
|
||||
) -> None:
|
||||
if self._read_only:
|
||||
return
|
||||
|
||||
# save time user already provided it
|
||||
current_text = self.line_edit.text()
|
||||
time_part = "00:00"
|
||||
|
||||
# extract time portion (last 5 characters of "dd.MM.yyyy hh:mm")
|
||||
if len(current_text) >= 16:
|
||||
potential_time = current_text[11:16]
|
||||
if "_" not in potential_time: # only if time was fully provided
|
||||
time_part = potential_time
|
||||
|
||||
if date and date.isValid():
|
||||
formatted_date = date.toString("dd.MM.yyyy")
|
||||
self.line_edit.setText(f"{formatted_date} {time_part}")
|
||||
self.line_edit.setStyleSheet("")
|
||||
else:
|
||||
self.line_edit.clear()
|
||||
|
||||
self.calendar.hide()
|
||||
|
||||
def set_datetime(
|
||||
self,
|
||||
dt: QDateTime,
|
||||
) -> None:
|
||||
if dt and dt.isValid():
|
||||
self.line_edit.setText(dt.toString("dd.MM.yyyy hh:mm"))
|
||||
self.line_edit.setStyleSheet("")
|
||||
else:
|
||||
self.line_edit.clear()
|
||||
|
||||
def set_pydatetime(
|
||||
self,
|
||||
dt: datetime.datetime,
|
||||
raise_error: bool = False,
|
||||
) -> None:
|
||||
dt_to_set = QDateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, 0)
|
||||
if raise_error and not dt_to_set.isValid():
|
||||
raise ValueError("Could not parse datetime object correctly")
|
||||
|
||||
self.set_datetime(dt_to_set)
|
||||
|
||||
def get_datetime(self) -> QDateTime:
|
||||
# valid or invalid QDateTime
|
||||
return QDateTime.fromString(self.line_edit.text(), "dd.MM.yyyy hh:mm")
|
||||
|
||||
def get_pydatetime(self) -> datetime.datetime:
|
||||
"""returns current value of the field as UTC datetime
|
||||
|
||||
Returns
|
||||
-------
|
||||
datetime.datetime
|
||||
UTC datetime object
|
||||
"""
|
||||
# valid or invalid datetime
|
||||
dt = self.get_datetime()
|
||||
py_dt = cast(datetime.datetime, dt.toPython())
|
||||
py_dt = py_dt.replace(tzinfo=TIMEZONE_CEST)
|
||||
|
||||
return py_dt.astimezone(datetime.UTC)
|
||||
|
||||
def reset(self) -> None:
|
||||
self.calendar.hide()
|
||||
self.line_edit.clear()
|
||||
|
||||
|
||||
class DynamicListWidget(QWidget):
|
||||
"""
|
||||
a widget, which can generate and manage an arbitrary number of sub forms.
|
||||
@@ -3206,7 +3651,13 @@ class Page_Consulting_ConsultingSession(QWidget):
|
||||
self.user_field = QLineEdit(
|
||||
placeholderText="wird über aktiven Nutzer bei Anlage gesetzt"
|
||||
)
|
||||
self.timestamp = QLineEdit() # TODO change to custom date field
|
||||
self.user_field.setReadOnly(True)
|
||||
self.user_field.setProperty("styleClass", "stempel")
|
||||
# ?? date or datetime?
|
||||
# self.timestamp = FlexibleDateInput()
|
||||
# self.timestamp.set_pydate(datetime.datetime.now())
|
||||
self.timestamp = FlexibleDateTimeInput()
|
||||
self.timestamp.set_pydatetime(datetime.datetime.now())
|
||||
|
||||
self.contact = QLineEdit(placeholderText="Freitext")
|
||||
self.contact_type = QLineEdit(placeholderText="sollte Dropdown sein") # Dropdown
|
||||
@@ -3260,7 +3711,7 @@ class Page_Consulting_ConsultingSession(QWidget):
|
||||
"[Page Consulting -- Consulting Session]: State = %s", self.STATE
|
||||
)
|
||||
set_widget_value(self.user_field, self.STATE.user_name)
|
||||
set_widget_value(self.timestamp, self.STATE.timestamp)
|
||||
set_widget_value(self.timestamp, self.STATE.timestamp.astimezone(TIMEZONE_CEST))
|
||||
set_widget_value(self.contact, self.STATE.partner)
|
||||
set_widget_value(self.contact_type, self.STATE.interaction_scheme)
|
||||
set_widget_value(self.crm_link, self.STATE.topic)
|
||||
@@ -3269,9 +3720,7 @@ class Page_Consulting_ConsultingSession(QWidget):
|
||||
|
||||
def _sync_gui_to_state(self) -> None:
|
||||
# TODO: get timestamp from custom field self.STATE.timestamp
|
||||
self.STATE.timestamp = datetime.datetime.strptime(
|
||||
self.timestamp.text(), "%d.%m.%Y %H:%M:%S"
|
||||
)
|
||||
self.STATE.timestamp = self.timestamp.get_pydatetime()
|
||||
self.STATE.partner = self.contact.text()
|
||||
self.STATE.interaction_scheme = self.contact_type.text()
|
||||
self.STATE.topic = self.crm_link.text() # TODO: change to correct link
|
||||
|
||||
Reference in New Issue
Block a user