diff --git a/application/blueprints/planning_consideration/forms.py b/application/blueprints/planning_consideration/forms.py index 03271ef..22e6469 100644 --- a/application/blueprints/planning_consideration/forms.py +++ b/application/blueprints/planning_consideration/forms.py @@ -87,3 +87,7 @@ class ConsiderationForm(FlaskForm): choices=[("public", "Public"), ("private", "Private")], default="public", ) + + +class NoteForm(FlaskForm): + text = TextAreaField("Note", validators=[DataRequired()]) diff --git a/application/blueprints/planning_consideration/views.py b/application/blueprints/planning_consideration/views.py index cb07a07..2838dec 100644 --- a/application/blueprints/planning_consideration/views.py +++ b/application/blueprints/planning_consideration/views.py @@ -1,6 +1,15 @@ import datetime -from flask import Blueprint, flash, redirect, render_template, request, session, url_for +from flask import ( + Blueprint, + abort, + flash, + redirect, + render_template, + request, + session, + url_for, +) from markupsafe import Markup from slugify import slugify @@ -10,6 +19,7 @@ FrequencyForm, LinkForm, LLCForm, + NoteForm, PriorityForm, PublicForm, StageForm, @@ -17,7 +27,7 @@ ) from application.extensions import db from application.forms import DeleteForm -from application.models import Consideration, FrequencyOfUpdates, Stage +from application.models import Consideration, FrequencyOfUpdates, Note, Stage from application.utils import login_required, true_false_to_bool planning_consideration = Blueprint( @@ -513,3 +523,61 @@ def edit_legislation(slug): return render_template( "questiontypes/input.html", consideration=consideration, form=form, page=page ) + + +@planning_consideration.route("//note", methods=["GET", "POST"]) +@login_required +def add_note(slug): + consideration = Consideration.query.filter(Consideration.slug == slug).first() + form = NoteForm() + + if form.validate_on_submit(): + note = Note(text=form.text.data, author=session["user"]["name"]) + if consideration.notes is None: + consideration.notes = [] + consideration.notes.append(note) + + db.session.add(consideration) + db.session.commit() + return redirect(url_for("planning_consideration.consideration", slug=slug)) + + page = {"title": "Add note", "submit_text": "Save note"} + + return render_template( + "questiontypes/input.html", consideration=consideration, form=form, page=page + ) + + +@planning_consideration.route("//note/", methods=["GET", "POST"]) +@login_required +def edit_note(slug, note_id): + consideration = Consideration.query.filter(Consideration.slug == slug).first() + note = Note.query.get(note_id) + if note is None or note.deleted_date is not None: + abort(404) + + form = NoteForm(obj=note) + + if form.validate_on_submit(): + note.text = form.text.data + note.author = session["user"]["name"] + + db.session.add(note) + db.session.commit() + return redirect(url_for("planning_consideration.consideration", slug=slug)) + + page = {"title": "Edit note", "submit_text": "Update note"} + + return render_template( + "questiontypes/input.html", consideration=consideration, form=form, page=page + ) + + +@planning_consideration.get("//note//delete") +@login_required +def delete_note(slug, note_id): + note = Note.query.get(note_id) + note.deleted_date = datetime.date.today() + db.session.add(note) + db.session.commit() + return redirect(url_for("planning_consideration.consideration", slug=slug)) diff --git a/application/models.py b/application/models.py index 9668237..1d7c253 100644 --- a/application/models.py +++ b/application/models.py @@ -84,6 +84,7 @@ class Consideration(DateModel): changes: Mapped[Optional[list]] = mapped_column(MutableList.as_mutable(JSONB)) is_local_land_charge: Mapped[bool] = mapped_column(Boolean, default=False) + notes: Mapped[List["Note"]] = relationship(back_populates="consideration") def delete(self): self.deleted_date = datetime.date.today() @@ -123,7 +124,7 @@ def receive_before_update(mapper, connection, target): modifications = {} state = db.inspect(target) for attr in state.attrs: - if attr.key not in ["changes", "udpated", "deleted_date"]: + if attr.key not in ["changes", "udpated", "deleted_date", "notes"]: history = attr.load_history() if history.has_changes(): c = {} @@ -219,6 +220,22 @@ def __repr__(self): return f" " +class Note(DateModel): + + id: Mapped[uuid.uuid4] = mapped_column( + UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 + ) + text: Mapped[str] = mapped_column(Text) + consideration_id: Mapped[uuid.uuid4] = mapped_column( + UUID(as_uuid=True), ForeignKey("consideration.id") + ) + consideration: Mapped[Consideration] = relationship(back_populates="notes") + author: Mapped[str] = mapped_column(Text) + + def __repr__(self): + return f"" + + # pydantic models diff --git a/application/templates/consideration.html b/application/templates/consideration.html index 373ea67..6b4f5f3 100644 --- a/application/templates/consideration.html +++ b/application/templates/consideration.html @@ -265,6 +265,34 @@

Design process stages

+
+ + {% if not config.AUTHENTICATION_ON or session["user"] %} +
+

Notes

+
    + {% for note in consideration.notes %} + {% if not note.deleted_date %} +
  • +

    {{ note.text }}

    + author: {{ note.author }} + added: {{ note.created }} +
    + + Edit note + + + Remove note + +
  • + {% endif %} + {% endfor %} +
+ +Add note +
+ {% endif %} + {% endblock content_primary %} {% block content_secondary %} diff --git a/migrations/versions/a37de03e6a59_add_notes_to_considerations_app.py b/migrations/versions/a37de03e6a59_add_notes_to_considerations_app.py new file mode 100644 index 0000000..d8a3d0a --- /dev/null +++ b/migrations/versions/a37de03e6a59_add_notes_to_considerations_app.py @@ -0,0 +1,37 @@ +"""add notes to considerations app + +Revision ID: a37de03e6a59 +Revises: 376575e0c068 +Create Date: 2024-05-20 10:27:29.764107 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = 'a37de03e6a59' +down_revision = '376575e0c068' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('note', + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('text', sa.Text(), nullable=False), + sa.Column('consideration_id', sa.UUID(), nullable=False), + sa.Column('author', sa.Text(), nullable=False), + sa.Column('created', sa.Date(), nullable=False), + sa.Column('updated', sa.DateTime(), nullable=True), + sa.Column('deleted_date', sa.Date(), nullable=True), + sa.ForeignKeyConstraint(['consideration_id'], ['consideration.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('note') + # ### end Alembic commands ###