From 2159d8a6f71739d5cdaa50df476472bf7937c9ca Mon Sep 17 00:00:00 2001 From: josihoppe <116898820+josihoppe@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:41:26 +0200 Subject: [PATCH 1/2] added django viewflow with bpmn flow structure --- building_dialouge_webapp/heat/admin.py | 12 +- building_dialouge_webapp/heat/flows.py | 28 ++++ building_dialouge_webapp/heat/forms.py | 60 +++++++- .../heat/migrations/0001_initial.py | 29 ++++ ...e_building_number_roof_windows_and_more.py | 62 ++++++++ ...ofprocess_remove_roof_building_and_more.py | 37 +++++ building_dialouge_webapp/heat/models.py | 44 +++++- building_dialouge_webapp/heat/urls.py | 13 ++ building_dialouge_webapp/heat/views.py | 143 +++++++++++++++++- .../templates/pages/heat_forms.html | 4 +- .../templates/pages/roof.html | 16 ++ .../templates/pages/roof_details_form.html | 11 ++ .../templates/pages/roof_insulation_form.html | 11 ++ .../templates/pages/roof_type_form.html | 11 ++ .../templates/pages/roof_usage_form.html | 11 ++ config/settings/base.py | 1 + 16 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 building_dialouge_webapp/heat/flows.py create mode 100644 building_dialouge_webapp/heat/migrations/0001_initial.py create mode 100644 building_dialouge_webapp/heat/migrations/0002_remove_building_number_roof_windows_and_more.py create mode 100644 building_dialouge_webapp/heat/migrations/0003_roofprocess_remove_roof_building_and_more.py create mode 100644 building_dialouge_webapp/templates/pages/roof.html create mode 100644 building_dialouge_webapp/templates/pages/roof_details_form.html create mode 100644 building_dialouge_webapp/templates/pages/roof_insulation_form.html create mode 100644 building_dialouge_webapp/templates/pages/roof_type_form.html create mode 100644 building_dialouge_webapp/templates/pages/roof_usage_form.html diff --git a/building_dialouge_webapp/heat/admin.py b/building_dialouge_webapp/heat/admin.py index 846f6b4..b03baba 100644 --- a/building_dialouge_webapp/heat/admin.py +++ b/building_dialouge_webapp/heat/admin.py @@ -1 +1,11 @@ -# Register your models here. +"""Add models to admin interface.""" + +from django.contrib import admin + +from building_dialouge_webapp.heat.models import Roof + +admin.site.register( + [ + Roof, + ], +) diff --git a/building_dialouge_webapp/heat/flows.py b/building_dialouge_webapp/heat/flows.py new file mode 100644 index 0000000..7b356a0 --- /dev/null +++ b/building_dialouge_webapp/heat/flows.py @@ -0,0 +1,28 @@ +from viewflow import this +from viewflow.workflow import act +from viewflow.workflow import flow + +from . import views +from .models import RoofProcess + + +class RoofProcessFlow(flow.Flow): + process_class = RoofProcess + + # Start the flow with the RoofTypeForm + start = flow.Start(views.RoofTypeView.as_view()).Next(this.split_roof_type) + + # Split the flow based on roof_type selected + split_roof_type = ( + flow.If(act.process.is_flat_roof) + .Then(this.roof_insulation) + .Else(this.roof_details) + ) + + roof_insulation = flow.View(views.RoofInsulationView.as_view()).Next(this.end) + + roof_details = flow.View(views.RoofDetailsView.as_view()).Next(this.roof_usage) + + roof_usage = flow.View(views.RoofUsageView.as_view()).Next(this.roof_insulation) + + end = flow.End() diff --git a/building_dialouge_webapp/heat/forms.py b/building_dialouge_webapp/heat/forms.py index 29e3555..4bc4a1b 100644 --- a/building_dialouge_webapp/heat/forms.py +++ b/building_dialouge_webapp/heat/forms.py @@ -1,5 +1,54 @@ +from crispy_bootstrap5.bootstrap5 import Switch +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Layout from django import forms +from .models import Roof + + +class RoofTypeForm(forms.ModelForm): + class Meta: + model = Roof + fields = ["roof_type"] + widgets = { + "roof_type": forms.RadioSelect( + choices=[ + ("flachdach", "Flachdach"), + ("satteldach", "Satteldach"), + ("walmdach", "Walmdach"), + ], + ), + } + + +class RoofDetailsForm(forms.ModelForm): + class Meta: + model = Roof + fields = ["roof_area", "roof_orientation", "number_roof_windows"] + + +class RoofUsageForm(forms.ModelForm): + class Meta: + model = Roof + fields = ["roof_usage"] + + +class RoofInsulationForm(forms.ModelForm): + class Meta: + model = Roof + fields = ["roof_insulation_exists"] + widgets = { + "roof_insulation_exists": forms.RadioSelect( + choices=[ + (True, "Yes"), + (False, "No"), + ], + ), + } + + +# old forms, not sure if we're gonna need them for the new forms + class ElectricityConsumptionForm(forms.Form): household_members = forms.ChoiceField( @@ -48,7 +97,6 @@ def clean(self): class ElectricityGenerationForm(forms.Form): pv_owned = forms.BooleanField( label="Haben Sie eine PV-Anlage?", - widget=forms.CheckboxInput(attrs={"class": "form-check-input"}), required=False, ) installed_pv_power = forms.IntegerField( @@ -76,6 +124,16 @@ class ElectricityGenerationForm(forms.Form): widget=forms.RadioSelect, ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Switch("pv_owned"), + "installed_pv_power", + "roof_cardinal_direction", + "roof_angle", + ) + def clean(self): cleaned_data = super().clean() pv_owned = cleaned_data.get("pv_owned") diff --git a/building_dialouge_webapp/heat/migrations/0001_initial.py b/building_dialouge_webapp/heat/migrations/0001_initial.py new file mode 100644 index 0000000..8df6590 --- /dev/null +++ b/building_dialouge_webapp/heat/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.12 on 2024-09-09 15:06 + +from django.db import migrations, models +import django_fsm + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Building', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('state', django_fsm.FSMField(default='start', max_length=50)), + ('roof_type', models.CharField(blank=True, choices=[('flachdach', 'Flachdach'), ('satteldach', 'Satteldach'), ('walmdach', 'Walmdach')], max_length=50, null=True)), + ('roof_area', models.FloatField(blank=True, null=True)), + ('roof_orientation', models.CharField(blank=True, max_length=100, null=True)), + ('number_roof_windows', models.IntegerField(blank=True, null=True)), + ('roof_usage', models.CharField(blank=True, max_length=100, null=True)), + ('roof_insulation_exists', models.BooleanField(blank=True, null=True)), + ('ventilation_system', models.CharField(blank=True, max_length=100, null=True)), + ], + ), + ] diff --git a/building_dialouge_webapp/heat/migrations/0002_remove_building_number_roof_windows_and_more.py b/building_dialouge_webapp/heat/migrations/0002_remove_building_number_roof_windows_and_more.py new file mode 100644 index 0000000..f3f0594 --- /dev/null +++ b/building_dialouge_webapp/heat/migrations/0002_remove_building_number_roof_windows_and_more.py @@ -0,0 +1,62 @@ +# Generated by Django 4.2.12 on 2024-09-23 09:51 + +from django.db import migrations, models +import django.db.models.deletion +import django_fsm + + +class Migration(migrations.Migration): + + dependencies = [ + ('heat', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='building', + name='number_roof_windows', + ), + migrations.RemoveField( + model_name='building', + name='roof_area', + ), + migrations.RemoveField( + model_name='building', + name='roof_insulation_exists', + ), + migrations.RemoveField( + model_name='building', + name='roof_orientation', + ), + migrations.RemoveField( + model_name='building', + name='roof_type', + ), + migrations.RemoveField( + model_name='building', + name='roof_usage', + ), + migrations.RemoveField( + model_name='building', + name='ventilation_system', + ), + migrations.AlterField( + model_name='building', + name='state', + field=django_fsm.FSMField(choices=[('Bestandsaufnahme', 'Bestandsaufnahme'), ('Dach', 'Dach'), ('Dach_in_progress', 'Dach_in_progress'), ('Fenster', 'Fenster')], default=('Bestandsaufnahme', 'Bestandsaufnahme'), max_length=50), + ), + migrations.CreateModel( + name='Roof', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('roof_state', django_fsm.FSMField(choices=[('roof_type', 'roof_type'), ('roof_type_flach', 'roof_type_flach'), ('roof_type_walm', 'roof_type_walm'), ('roof_details', 'roof_details'), ('roof_insulation', 'roof_insulation'), ('ventilation_system', 'ventilation_system')], default=('roof_type', 'roof_type'), max_length=50)), + ('roof_type', models.CharField(blank=True, choices=[('flachdach', 'Flachdach'), ('satteldach', 'Satteldach'), ('walmdach', 'Walmdach')], max_length=50, null=True)), + ('roof_area', models.FloatField(blank=True, null=True)), + ('roof_orientation', models.CharField(blank=True, max_length=100, null=True)), + ('number_roof_windows', models.IntegerField(blank=True, null=True)), + ('roof_usage', models.CharField(blank=True, max_length=100, null=True)), + ('roof_insulation_exists', models.BooleanField(blank=True, null=True)), + ('building', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='heat.building')), + ], + ), + ] diff --git a/building_dialouge_webapp/heat/migrations/0003_roofprocess_remove_roof_building_and_more.py b/building_dialouge_webapp/heat/migrations/0003_roofprocess_remove_roof_building_and_more.py new file mode 100644 index 0000000..dcd1f9b --- /dev/null +++ b/building_dialouge_webapp/heat/migrations/0003_roofprocess_remove_roof_building_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 4.2.12 on 2024-10-15 15:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('viewflow_base', '__first__'), + ('heat', '0002_remove_building_number_roof_windows_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='RoofProcess', + fields=[ + ], + options={ + 'verbose_name_plural': 'Roof process', + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('viewflow_base.process',), + ), + migrations.RemoveField( + model_name='roof', + name='building', + ), + migrations.RemoveField( + model_name='roof', + name='roof_state', + ), + migrations.DeleteModel( + name='Building', + ), + ] diff --git a/building_dialouge_webapp/heat/models.py b/building_dialouge_webapp/heat/models.py index 6b20219..5cdba18 100644 --- a/building_dialouge_webapp/heat/models.py +++ b/building_dialouge_webapp/heat/models.py @@ -1 +1,43 @@ -# Create your models here. +from django.db import models +from viewflow.workflow.models import Process + + +class Roof(models.Model): + """Model for roof.""" + + roof_type = models.CharField( + max_length=50, + choices=[ + ("flachdach", "Flachdach"), + ("satteldach", "Satteldach"), + ("walmdach", "Walmdach"), + ], + blank=True, + ) + roof_area = models.FloatField(blank=True) + roof_orientation = models.CharField(max_length=100, blank=True) + number_roof_windows = models.IntegerField(blank=True) + roof_usage = models.CharField(max_length=100, blank=True) + roof_insulation_exists = models.BooleanField(null=True, blank=True) + + def __str__(self): + if self.pk: + return f"#{self.roof_type}" + return super().__str__() + + +class RoofProcess(Process): + """ + This model extends the base viewflow `Process` model. + + """ + + class Meta: + proxy = True + verbose_name_plural = "Roof process" + + def is_flat_roof(self): + try: + return self.artifact.roof.roof_type == "Flachdach" + except Roof.DoesNotExist: + return None diff --git a/building_dialouge_webapp/heat/urls.py b/building_dialouge_webapp/heat/urls.py index a11daec..ea76519 100644 --- a/building_dialouge_webapp/heat/urls.py +++ b/building_dialouge_webapp/heat/urls.py @@ -6,4 +6,17 @@ urlpatterns = [ path("forms/", views.handle_forms, name="forms"), + # BPMN Views + path("roof_type/", views.RoofTypeView.as_view(), name="roof_type_form"), + path( + "roof_insulation//", + views.RoofInsulationView.as_view(), + name="roof_insulation", + ), + path( + "roof_details//", + views.RoofDetailsView.as_view(), + name="roof_details", + ), + path("roof_usage//", views.RoofUsageView.as_view(), name="roof_usage"), ] diff --git a/building_dialouge_webapp/heat/views.py b/building_dialouge_webapp/heat/views.py index cffefec..eed34e6 100644 --- a/building_dialouge_webapp/heat/views.py +++ b/building_dialouge_webapp/heat/views.py @@ -1,5 +1,9 @@ from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.shortcuts import redirect from django.shortcuts import render +from django.views import generic +from viewflow.workflow.flow.views import TaskSuccessUrlMixin from .forms import ElectricityConsumptionForm from .forms import ElectricityGenerationForm @@ -8,6 +12,13 @@ from .forms import HeatGenerationForm from .forms import HeatGenerationForm2 from .forms import HeatStorageForm +from .forms import RoofDetailsForm +from .forms import RoofInsulationForm +from .forms import RoofTypeForm +from .forms import RoofUsageForm +from .models import Roof + +# old implementation of forms, using it for instatiating Roof model def handle_forms(request): @@ -34,8 +45,9 @@ def handle_forms(request): HeatStorageForm, ] + roof = Roof.objects.create() # noqa: F841 + if request.method == "POST": - # Instantiate form instances with submitted data form_instances = [f(request.POST) for f in form_classes] all_valid = all(form.is_valid() for form in form_instances) @@ -52,3 +64,132 @@ def handle_forms(request): } return render(request, "pages/heat_forms.html", context) + + +# BPMN Flow - views + + +def roof_type_view(request): + roof_instance = request.activation.process + + if request.method == "POST": + form = RoofTypeForm(request.POST, instance=roof_instance) + if form.is_valid(): + form.save() + request.activation.execute() + return redirect(request.activation.get_next_url()) + + else: + form = RoofTypeForm(instance=roof_instance) + + return render(request, "pages/roof_type_form.html", {"form": form}) + + +def roof_insulation_view(request): + roof_instance = request.activation.process + + if request.method == "POST": + form = RoofInsulationForm(request.POST, instance=roof_instance) + if form.is_valid(): + form.save() + request.activation.execute() + return redirect(request.activation.get_next_url()) + else: + form = RoofInsulationForm(instance=roof_instance) + + return render(request, "pages/roof_insulation_form.html", {"form": form}) + + +def roof_details_view(request): + roof_instance = request.activation.process + + if request.method == "POST": + form = RoofDetailsForm(request.POST, instance=roof_instance) + if form.is_valid(): + form.save() + request.activation.execute() + return redirect(request.activation.get_next_url()) + else: + form = RoofDetailsForm(instance=roof_instance) + + return render(request, "pages/roof_details_form.html", {"form": form}) + + +def roof_usage_view(request): + roof_instance = request.activation.process.artifact + + if request.method == "POST": + form = RoofUsageForm(request.POST, instance=roof_instance) + if form.is_valid(): + form.save() + request.activation.execute() + return redirect(request.activation.get_next_url()) + else: + form = RoofUsageForm(instance=roof_instance) + + return render(request, "pages/roof_usage_form.html", {"form": form}) + + +class RoofTypeView(TaskSuccessUrlMixin, generic.CreateView): + model = Roof + form_class = RoofTypeForm + template_name = "pages/roof_type_form.html" + + def form_valid(self, form): + self.object = form.save() + + self.request.activation.process.artifact = self.object + + self.request.activation.execute() + return HttpResponseRedirect(self.get_success_url()) + + +class RoofInsulationView(TaskSuccessUrlMixin, generic.UpdateView): + model = Roof + form_class = RoofInsulationForm + template_name = "pages/roof_insulation_form.html" + + def get_object(self): + return self.request.activation.process.artifact + + def form_valid(self, form): + self.object = form.save() + + self.request.activation.process.artifact = self.object + + self.request.activation.execute() + return HttpResponseRedirect(self.get_success_url()) + + +class RoofDetailsView(TaskSuccessUrlMixin, generic.UpdateView): + model = Roof + form_class = RoofDetailsForm + template_name = "pages/roof_details_form.html" + + def get_object(self): + return self.request.activation.process.artifact + + def form_valid(self, form): + self.object = form.save() + + self.request.activation.process.artifact = self.object + + self.request.activation.execute() + return HttpResponseRedirect(self.get_success_url()) + + +class RoofUsageView(TaskSuccessUrlMixin, generic.UpdateView): + model = Roof + form_class = RoofUsageForm + template_name = "pages/roof_usage_form.html" + + def get_object(self): + return self.request.activation.process.artifact + + def form_valid(self, form): + self.object = form.save() + + self.request.activation.process.artifact = self.object + + self.request.activation.execute() + return HttpResponseRedirect(self.get_success_url()) diff --git a/building_dialouge_webapp/templates/pages/heat_forms.html b/building_dialouge_webapp/templates/pages/heat_forms.html index 7b00acc..eba4f16 100644 --- a/building_dialouge_webapp/templates/pages/heat_forms.html +++ b/building_dialouge_webapp/templates/pages/heat_forms.html @@ -1,11 +1,13 @@ {% extends "base.html" %} +{% load crispy_forms_tags %} + {% block content %}
{% csrf_token %} {% for form in form_instances %}
- {{ form.as_p }} + {{ form|crispy }}
{% endfor %} diff --git a/building_dialouge_webapp/templates/pages/roof.html b/building_dialouge_webapp/templates/pages/roof.html new file mode 100644 index 0000000..61c9da6 --- /dev/null +++ b/building_dialouge_webapp/templates/pages/roof.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block content %} + + {% csrf_token %} + {% for form in form_instances %} +
+ {{ form|crispy }} +
+
+ {% endfor %} + +
+{% endblock content %} diff --git a/building_dialouge_webapp/templates/pages/roof_details_form.html b/building_dialouge_webapp/templates/pages/roof_details_form.html new file mode 100644 index 0000000..6ba457e --- /dev/null +++ b/building_dialouge_webapp/templates/pages/roof_details_form.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block content %} +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock content %} diff --git a/building_dialouge_webapp/templates/pages/roof_insulation_form.html b/building_dialouge_webapp/templates/pages/roof_insulation_form.html new file mode 100644 index 0000000..6ba457e --- /dev/null +++ b/building_dialouge_webapp/templates/pages/roof_insulation_form.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block content %} +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock content %} diff --git a/building_dialouge_webapp/templates/pages/roof_type_form.html b/building_dialouge_webapp/templates/pages/roof_type_form.html new file mode 100644 index 0000000..0efbae4 --- /dev/null +++ b/building_dialouge_webapp/templates/pages/roof_type_form.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block content %} +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock content %} diff --git a/building_dialouge_webapp/templates/pages/roof_usage_form.html b/building_dialouge_webapp/templates/pages/roof_usage_form.html new file mode 100644 index 0000000..6ba457e --- /dev/null +++ b/building_dialouge_webapp/templates/pages/roof_usage_form.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% load crispy_forms_tags %} + +{% block content %} +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock content %} diff --git a/config/settings/base.py b/config/settings/base.py index efafed8..a459a3a 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -78,6 +78,7 @@ "allauth.mfa", "allauth.socialaccount", "django_celery_beat", + "viewflow", ] LOCAL_APPS = [ From 1e31167ccf283d351997bc0396308caaea8073ef Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Fri, 18 Oct 2024 13:59:06 +0200 Subject: [PATCH 2/2] Could start BPMN flow (but we will not follow this viewflow path) --- .idea/building_dialouge_webapp.iml | 11 +- .idea/misc.xml | 4 + .idea/ruff.xml | 6 + .idea/runConfigurations/migrate.xml | 32 ----- .idea/runConfigurations/runserver.xml | 33 ------ .idea/runConfigurations/runserver_plus.xml | 14 ++- building_dialouge_webapp/heat/flows.py | 17 ++- .../heat/migrations/0001_initial.py | 29 ----- ...e_building_number_roof_windows_and_more.py | 62 ---------- ...ofprocess_remove_roof_building_and_more.py | 37 ------ building_dialouge_webapp/heat/models.py | 6 +- building_dialouge_webapp/heat/urls.py | 13 +- building_dialouge_webapp/heat/views.py | 11 +- .../templates/pages/roof_type_form.html | 5 +- building_dialouge_webapp/users/middleware.py | 111 ++++++++++++++++++ config/settings/base.py | 2 + config/urls.py | 22 +++- requirements/base.txt | 1 + 18 files changed, 186 insertions(+), 230 deletions(-) create mode 100644 .idea/ruff.xml delete mode 100644 .idea/runConfigurations/migrate.xml delete mode 100644 .idea/runConfigurations/runserver.xml delete mode 100644 building_dialouge_webapp/heat/migrations/0001_initial.py delete mode 100644 building_dialouge_webapp/heat/migrations/0002_remove_building_number_roof_windows_and_more.py delete mode 100644 building_dialouge_webapp/heat/migrations/0003_roofprocess_remove_roof_building_and_more.py create mode 100644 building_dialouge_webapp/users/middleware.py diff --git a/.idea/building_dialouge_webapp.iml b/.idea/building_dialouge_webapp.iml index 4ea41e6..7d5afe6 100644 --- a/.idea/building_dialouge_webapp.iml +++ b/.idea/building_dialouge_webapp.iml @@ -13,19 +13,19 @@ - - - + + + + - + - @@ -37,7 +37,6 @@ - diff --git a/.idea/misc.xml b/.idea/misc.xml index 10af178..2857af7 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,10 @@ + + + diff --git a/.idea/ruff.xml b/.idea/ruff.xml new file mode 100644 index 0000000..5c839d6 --- /dev/null +++ b/.idea/ruff.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/.idea/runConfigurations/migrate.xml b/.idea/runConfigurations/migrate.xml deleted file mode 100644 index 1210686..0000000 --- a/.idea/runConfigurations/migrate.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/.idea/runConfigurations/runserver.xml b/.idea/runConfigurations/runserver.xml deleted file mode 100644 index ef9a7b2..0000000 --- a/.idea/runConfigurations/runserver.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - diff --git a/.idea/runConfigurations/runserver_plus.xml b/.idea/runConfigurations/runserver_plus.xml index 8045e7b..23f670c 100644 --- a/.idea/runConfigurations/runserver_plus.xml +++ b/.idea/runConfigurations/runserver_plus.xml @@ -1,17 +1,29 @@ + diff --git a/building_dialouge_webapp/heat/flows.py b/building_dialouge_webapp/heat/flows.py index 7b356a0..8eb63f9 100644 --- a/building_dialouge_webapp/heat/flows.py +++ b/building_dialouge_webapp/heat/flows.py @@ -1,5 +1,6 @@ +from typing import Any + from viewflow import this -from viewflow.workflow import act from viewflow.workflow import flow from . import views @@ -14,9 +15,9 @@ class RoofProcessFlow(flow.Flow): # Split the flow based on roof_type selected split_roof_type = ( - flow.If(act.process.is_flat_roof) - .Then(this.roof_insulation) - .Else(this.roof_details) + flow.Switch() + .Case(this.roof_insulation, lambda act: act.process.roof_type == "Flachdach") + .Default(this.roof_details) ) roof_insulation = flow.View(views.RoofInsulationView.as_view()).Next(this.end) @@ -26,3 +27,11 @@ class RoofProcessFlow(flow.Flow): roof_usage = flow.View(views.RoofUsageView.as_view()).Next(this.roof_insulation) end = flow.End() + + def has_view_permission(self, user: Any, obj: Any | None = None) -> bool: + return True + + +class RoofViewset(flow.FlowViewset): + def has_view_permission(self, user, obj=None): + return True diff --git a/building_dialouge_webapp/heat/migrations/0001_initial.py b/building_dialouge_webapp/heat/migrations/0001_initial.py deleted file mode 100644 index 8df6590..0000000 --- a/building_dialouge_webapp/heat/migrations/0001_initial.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 4.2.12 on 2024-09-09 15:06 - -from django.db import migrations, models -import django_fsm - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Building', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('state', django_fsm.FSMField(default='start', max_length=50)), - ('roof_type', models.CharField(blank=True, choices=[('flachdach', 'Flachdach'), ('satteldach', 'Satteldach'), ('walmdach', 'Walmdach')], max_length=50, null=True)), - ('roof_area', models.FloatField(blank=True, null=True)), - ('roof_orientation', models.CharField(blank=True, max_length=100, null=True)), - ('number_roof_windows', models.IntegerField(blank=True, null=True)), - ('roof_usage', models.CharField(blank=True, max_length=100, null=True)), - ('roof_insulation_exists', models.BooleanField(blank=True, null=True)), - ('ventilation_system', models.CharField(blank=True, max_length=100, null=True)), - ], - ), - ] diff --git a/building_dialouge_webapp/heat/migrations/0002_remove_building_number_roof_windows_and_more.py b/building_dialouge_webapp/heat/migrations/0002_remove_building_number_roof_windows_and_more.py deleted file mode 100644 index f3f0594..0000000 --- a/building_dialouge_webapp/heat/migrations/0002_remove_building_number_roof_windows_and_more.py +++ /dev/null @@ -1,62 +0,0 @@ -# Generated by Django 4.2.12 on 2024-09-23 09:51 - -from django.db import migrations, models -import django.db.models.deletion -import django_fsm - - -class Migration(migrations.Migration): - - dependencies = [ - ('heat', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='building', - name='number_roof_windows', - ), - migrations.RemoveField( - model_name='building', - name='roof_area', - ), - migrations.RemoveField( - model_name='building', - name='roof_insulation_exists', - ), - migrations.RemoveField( - model_name='building', - name='roof_orientation', - ), - migrations.RemoveField( - model_name='building', - name='roof_type', - ), - migrations.RemoveField( - model_name='building', - name='roof_usage', - ), - migrations.RemoveField( - model_name='building', - name='ventilation_system', - ), - migrations.AlterField( - model_name='building', - name='state', - field=django_fsm.FSMField(choices=[('Bestandsaufnahme', 'Bestandsaufnahme'), ('Dach', 'Dach'), ('Dach_in_progress', 'Dach_in_progress'), ('Fenster', 'Fenster')], default=('Bestandsaufnahme', 'Bestandsaufnahme'), max_length=50), - ), - migrations.CreateModel( - name='Roof', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('roof_state', django_fsm.FSMField(choices=[('roof_type', 'roof_type'), ('roof_type_flach', 'roof_type_flach'), ('roof_type_walm', 'roof_type_walm'), ('roof_details', 'roof_details'), ('roof_insulation', 'roof_insulation'), ('ventilation_system', 'ventilation_system')], default=('roof_type', 'roof_type'), max_length=50)), - ('roof_type', models.CharField(blank=True, choices=[('flachdach', 'Flachdach'), ('satteldach', 'Satteldach'), ('walmdach', 'Walmdach')], max_length=50, null=True)), - ('roof_area', models.FloatField(blank=True, null=True)), - ('roof_orientation', models.CharField(blank=True, max_length=100, null=True)), - ('number_roof_windows', models.IntegerField(blank=True, null=True)), - ('roof_usage', models.CharField(blank=True, max_length=100, null=True)), - ('roof_insulation_exists', models.BooleanField(blank=True, null=True)), - ('building', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='heat.building')), - ], - ), - ] diff --git a/building_dialouge_webapp/heat/migrations/0003_roofprocess_remove_roof_building_and_more.py b/building_dialouge_webapp/heat/migrations/0003_roofprocess_remove_roof_building_and_more.py deleted file mode 100644 index dcd1f9b..0000000 --- a/building_dialouge_webapp/heat/migrations/0003_roofprocess_remove_roof_building_and_more.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 4.2.12 on 2024-10-15 15:22 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('viewflow_base', '__first__'), - ('heat', '0002_remove_building_number_roof_windows_and_more'), - ] - - operations = [ - migrations.CreateModel( - name='RoofProcess', - fields=[ - ], - options={ - 'verbose_name_plural': 'Roof process', - 'proxy': True, - 'indexes': [], - 'constraints': [], - }, - bases=('viewflow_base.process',), - ), - migrations.RemoveField( - model_name='roof', - name='building', - ), - migrations.RemoveField( - model_name='roof', - name='roof_state', - ), - migrations.DeleteModel( - name='Building', - ), - ] diff --git a/building_dialouge_webapp/heat/models.py b/building_dialouge_webapp/heat/models.py index 5cdba18..dacd5ab 100644 --- a/building_dialouge_webapp/heat/models.py +++ b/building_dialouge_webapp/heat/models.py @@ -1,4 +1,5 @@ from django.db import models +from viewflow.jsonstore import CharField from viewflow.workflow.models import Process @@ -29,12 +30,13 @@ def __str__(self): class RoofProcess(Process): """ This model extends the base viewflow `Process` model. - """ + roof_type_chosen = CharField() + class Meta: - proxy = True verbose_name_plural = "Roof process" + proxy = True def is_flat_roof(self): try: diff --git a/building_dialouge_webapp/heat/urls.py b/building_dialouge_webapp/heat/urls.py index ea76519..756000c 100644 --- a/building_dialouge_webapp/heat/urls.py +++ b/building_dialouge_webapp/heat/urls.py @@ -4,19 +4,8 @@ app_name = "heat" + urlpatterns = [ path("forms/", views.handle_forms, name="forms"), # BPMN Views - path("roof_type/", views.RoofTypeView.as_view(), name="roof_type_form"), - path( - "roof_insulation//", - views.RoofInsulationView.as_view(), - name="roof_insulation", - ), - path( - "roof_details//", - views.RoofDetailsView.as_view(), - name="roof_details", - ), - path("roof_usage//", views.RoofUsageView.as_view(), name="roof_usage"), ] diff --git a/building_dialouge_webapp/heat/views.py b/building_dialouge_webapp/heat/views.py index eed34e6..544d233 100644 --- a/building_dialouge_webapp/heat/views.py +++ b/building_dialouge_webapp/heat/views.py @@ -45,8 +45,6 @@ def handle_forms(request): HeatStorageForm, ] - roof = Roof.objects.create() # noqa: F841 - if request.method == "POST": form_instances = [f(request.POST) for f in form_classes] all_valid = all(form.is_valid() for form in form_instances) @@ -130,16 +128,13 @@ def roof_usage_view(request): return render(request, "pages/roof_usage_form.html", {"form": form}) -class RoofTypeView(TaskSuccessUrlMixin, generic.CreateView): +class RoofTypeView(TaskSuccessUrlMixin, generic.FormView): model = Roof form_class = RoofTypeForm template_name = "pages/roof_type_form.html" def form_valid(self, form): - self.object = form.save() - - self.request.activation.process.artifact = self.object - + self.request.activation.process.roof_type = form.cleaned_data["roof_type"] self.request.activation.execute() return HttpResponseRedirect(self.get_success_url()) @@ -157,7 +152,7 @@ def form_valid(self, form): self.request.activation.process.artifact = self.object - self.request.activation.execute() + self.request.activation return HttpResponseRedirect(self.get_success_url()) diff --git a/building_dialouge_webapp/templates/pages/roof_type_form.html b/building_dialouge_webapp/templates/pages/roof_type_form.html index 0efbae4..a9bb93b 100644 --- a/building_dialouge_webapp/templates/pages/roof_type_form.html +++ b/building_dialouge_webapp/templates/pages/roof_type_form.html @@ -1,11 +1,10 @@ -{% extends "base.html" %} - +{#{% extends "base.html" %}#} {% load crispy_forms_tags %} {% block content %}
{% csrf_token %} {{ form.as_p }} - +
{% endblock content %} diff --git a/building_dialouge_webapp/users/middleware.py b/building_dialouge_webapp/users/middleware.py new file mode 100644 index 0000000..e285c84 --- /dev/null +++ b/building_dialouge_webapp/users/middleware.py @@ -0,0 +1,111 @@ +from allauth.account.models import EmailAddress +from allauth.account.utils import perform_login +from django.conf import settings +from django.contrib.auth import get_user_model +from django.contrib.auth import login +from django.core.exceptions import ImproperlyConfigured +from django.utils.deprecation import MiddlewareMixin + +DEFAULT_USERNAME = "default" + + +class AlwaysAuthenticateMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + @staticmethod + def login_dummy_user(request): + if request.user.is_authenticated: + return request + + # Get the user model (usually this is the `User` model from `django.contrib.auth.models`) + User = get_user_model() + + # Create a dummy user if it doesn't exist already + email = "dummyuser@example.com" + username = "dummyuser" + password = "dummypassword123" + + user, created = User.objects.get_or_create( + username=username, + defaults={ + "email": email, + }, + ) + + # If the user is created for the first time, set a password + if created: + user.set_password(password) + user.save() + + # Associate email address (this is important if you're using allauth's email confirmation) + EmailAddress.objects.create( + user=user, + email=email, + verified=True, + primary=True, + ) + + # Log in the dummy user + perform_login( + request, + user, + email_verification=settings.ACCOUNT_EMAIL_VERIFICATION, + ) + return request + + def __call__(self, request): + request = self.login_dummy_user(request) + response = self.get_response(request) + + # Code to be executed for each request/response after + # the view is called. + + return response + + +class AlwaysAuthenticatedMiddleware(MiddlewareMixin): + """ + Ensures that the request has an authenticated user. + + If the request doesn't have an authenticated user, it logs in a default + user. If the default user doesn't exist, it is created. + + Will raise an ImproperlyConfiguredException when DEBUG=False, unless + ALWAYS_AUTHENTICATED_DEBUG_ONLY is set to False. + + This middleware reads these settings: + * ALWAYS_AUTHENTICATED_USERNAME (string): + the name of the default user, defaults to `'user'`. + * ALWAYS_AUTHENTICATED_USER_DEFAULTS (dict): + additional default values to set when creating the user. + * ALWAYS_AUTHENTICATED_DEBUG_ONLY: + Set to `False` to allow running with DEBUG=False. + """ + + def __init__(self, *args, **kwargs): + self.username = getattr(settings, "ALWAYS_AUTHENTICATED_USERNAME", "user") + self.defaults = getattr(settings, "ALWAYS_AUTHENTICATED_USER_DEFAULTS", {}) + if not settings.DEBUG and getattr( + settings, + "ALWAYS_AUTHENTICATED_DEBUG_ONLY", + True, + ): + raise ImproperlyConfigured( + "DEBUG=%s, but AlwaysAuthenticatedMiddleware is configured to " + "only run in debug mode.\n" + "Remove AlwaysAuthenticatedMiddleware from " + "MIDDLEWARE/MIDDLEWARE_CLASSES or set " + "ALWAYS_AUTHENTICATED_DEBUG_ONLY to False." % settings.DEBUG, + ) + super(AlwaysAuthenticatedMiddleware, self).__init__(*args, **kwargs) + + def process_request(self, request): + if not request.user.is_authenticated: + user, created = User.objects.get_or_create( + username=self.username, + defaults=self.defaults, + ) + + user.backend = settings.AUTHENTICATION_BACKENDS[0] + login(request, user) diff --git a/config/settings/base.py b/config/settings/base.py index a459a3a..ba7da3c 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -79,6 +79,7 @@ "allauth.socialaccount", "django_celery_beat", "viewflow", + "viewflow.workflow", ] LOCAL_APPS = [ @@ -141,6 +142,7 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "users.middleware.AlwaysAuthenticateMiddleware", "allauth.account.middleware.AccountMiddleware", ] diff --git a/config/urls.py b/config/urls.py index e08dc93..ad2cc62 100644 --- a/config/urls.py +++ b/config/urls.py @@ -7,9 +7,29 @@ from django.views import defaults as default_views from django.views.generic import TemplateView +from viewflow.urls import Application, Site + +from building_dialouge_webapp.heat.flows import RoofProcessFlow, RoofViewset + + +site = Site( + title="ACME Corp", + viewsets=[ + Application( + title="Sample App", + icon="people", + app_name="heat", + viewsets=[ + RoofViewset(RoofProcessFlow), + ], + ), + ], +) + urlpatterns = [ path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), - path("", include("building_dialouge_webapp.heat.urls", namespace="heat")), + # path("", include("building_dialouge_webapp.heat.urls", namespace="heat")), + path("", site.urls), path( "about/", TemplateView.as_view(template_name="pages/about.html"), diff --git a/requirements/base.txt b/requirements/base.txt index c8efc8d..319f703 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -19,3 +19,4 @@ django-crispy-forms==2.1 # https://github.com/django-crispy-forms/django-crispy crispy-bootstrap5==2024.2 # https://github.com/django-crispy-forms/crispy-bootstrap5 django-compressor==4.4 # https://github.com/django-compressor/django-compressor django-redis==5.4.0 # https://github.com/jazzband/django-redis +django-viewflow==2.2.8 # https://github.com/viewflow/viewflow