forms.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. from __future__ import annotations
  2. import copy
  3. from flask import current_app
  4. from flask_security import current_user
  5. from flask_wtf import FlaskForm
  6. from sqlalchemy import select
  7. from wtforms import StringField, DecimalField, SelectField, IntegerField
  8. from wtforms.validators import DataRequired, optional
  9. from flexmeasures.auth.policy import user_has_admin_access
  10. from flexmeasures.data import db
  11. from flexmeasures.data.models.generic_assets import GenericAssetType
  12. from flexmeasures.data.models.user import Account
  13. class AssetForm(FlaskForm):
  14. """The default asset form only allows to edit the name and location."""
  15. name = StringField("Name")
  16. latitude = DecimalField(
  17. "Latitude",
  18. validators=[optional()],
  19. places=None,
  20. render_kw={"placeholder": "--Click the map or enter a latitude--"},
  21. )
  22. longitude = DecimalField(
  23. "Longitude",
  24. validators=[optional()],
  25. places=None,
  26. render_kw={"placeholder": "--Click the map or enter a longitude--"},
  27. )
  28. attributes = StringField("Other attributes (JSON)", default="{}")
  29. def validate_on_submit(self):
  30. if (
  31. hasattr(self, "generic_asset_type_id")
  32. and self.generic_asset_type_id.data == -1
  33. ):
  34. self.generic_asset_type_id.data = (
  35. "" # cannot be coerced to int so will be flagged as invalid input
  36. )
  37. if hasattr(self, "account_id") and self.account_id.data == -1:
  38. del self.account_id # asset will be public
  39. result = super().validate_on_submit()
  40. return result
  41. def to_json(self) -> dict:
  42. """turn form data into a JSON we can POST to our internal API"""
  43. data = copy.copy(self.data)
  44. if data.get("longitude") is not None:
  45. data["longitude"] = float(data["longitude"])
  46. if data.get("latitude") is not None:
  47. data["latitude"] = float(data["latitude"])
  48. if data.get("parent_asset_id") is not None:
  49. data["parent_asset_id"] = int(data["parent_asset_id"])
  50. if "csrf_token" in data:
  51. del data["csrf_token"]
  52. return data
  53. def process_api_validation_errors(self, api_response: dict):
  54. """Process form errors from the API for the WTForm"""
  55. if not isinstance(api_response, dict):
  56. return
  57. for error_header in ("json", "validation_errors"):
  58. if error_header not in api_response:
  59. continue
  60. for field in list(self._fields.keys()):
  61. if field in list(api_response[error_header].keys()):
  62. field_errors = api_response[error_header][field]
  63. if isinstance(field_errors, list):
  64. self._fields[field].errors += api_response[error_header][field]
  65. else:
  66. self._fields[field].errors.append(
  67. api_response[error_header][field]
  68. )
  69. def with_options(self):
  70. if "generic_asset_type_id" in self:
  71. self.generic_asset_type_id.choices = [(-1, "--Select type--")] + [
  72. (atype.id, atype.name)
  73. for atype in db.session.scalars(select(GenericAssetType)).all()
  74. ]
  75. if "account_id" in self:
  76. self.account_id.choices = [(-1, "--Select account--")] + [
  77. (account.id, account.name)
  78. for account in db.session.scalars(select(Account)).all()
  79. ]
  80. class NewAssetForm(AssetForm):
  81. """Here, in addition, we allow to set asset type and account."""
  82. generic_asset_type_id = SelectField(
  83. "Asset type", coerce=int, validators=[DataRequired()]
  84. )
  85. account_id = SelectField("Account", coerce=int)
  86. parent_asset_id = IntegerField(
  87. "Parent Asset Id", validators=[optional()]
  88. ) # Add parent_id field
  89. def set_account(self) -> tuple[Account | None, str | None]:
  90. """Set an account for the to-be-created asset.
  91. Return the account (if available) and an error message"""
  92. account_error = None
  93. if self.account_id.data == -1:
  94. if user_has_admin_access(current_user, "update"):
  95. return None, None # Account can be None (public asset)
  96. else:
  97. account_error = "Please pick an existing account."
  98. account = db.session.execute(
  99. select(Account).filter_by(id=int(self.account_id.data))
  100. ).scalar_one_or_none()
  101. if account:
  102. self.account_id.data = account.id
  103. else:
  104. current_app.logger.error(account_error)
  105. return account, account_error
  106. def set_asset_type(self) -> tuple[GenericAssetType | None, str | None]:
  107. """Set an asset type for the to-be-created asset.
  108. Return the asset type (if available) and an error message."""
  109. asset_type = None
  110. asset_type_error = None
  111. if int(self.generic_asset_type_id.data) == -1:
  112. asset_type_error = "Pick an existing asset type."
  113. else:
  114. asset_type = db.session.execute(
  115. select(GenericAssetType).filter_by(
  116. id=int(self.generic_asset_type_id.data)
  117. )
  118. ).scalar_one_or_none()
  119. if asset_type:
  120. self.generic_asset_type_id.data = asset_type.id
  121. else:
  122. current_app.logger.error(asset_type_error)
  123. return asset_type, asset_type_error