Skip to content

Project/scenario overviews and project-based permissions - #39

Open
PaulScheerRLI wants to merge 15 commits into
devfrom
feature/project-overviews
Open

Project/scenario overviews and project-based permissions#39
PaulScheerRLI wants to merge 15 commits into
devfrom
feature/project-overviews

Conversation

@PaulScheerRLI

@PaulScheerRLI PaulScheerRLI commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Second of two stacked PRs splitting feature/geometry_importbased on feature/geodata-import (#38); retarget to dev after #38 merges. The diff shown here is only the overview/permission work.
This is meant to make review easier

What

  • Project model bundling scenarios; access via per-project group with guardian object permissions (migration 0019)
  • NOTE: New permission system can lead to scenarios, where you dont have permissions after migrating
  • during development the following things should work
    • Create a new scenario with /?new=MyScenario
    • Delete stale/old Scenarios/Projects
    • Delete old groups
    • clean up guardian permissions via uv run manage.py clean_orphan_obj_perms

Projects /projects/

Manage Projects inside user projects space

  • Create empty Project or based on a public imported data scenario
  • Duplicate Project via ellipsis
  • Delete Project via ellipsis
  • Rename Project and Description
  • User Management is another PR
    @mosc5 right now everyone with "details" permission can duplicate/rename and delete projects.
  • duplication of projects does not transfer area permissions right now. Area Managers have to make areas public again

Scenarios / Project Overview /project_overview/project_uuid/

Manage Scenarios for a given project

  • Duplicate Scenarios

  • Delete Scenarios

  • Rename Scenario and Description
    @mosc5 right now everyone with "details" permission can duplicate/rename and delete scenarios.

  • duplication of scenarios does transfer area permissions.

  • minor issue: Deepcopying the scenarios creates objects in a non-deterministic way. When ordering objects/areas based on creation time, this can rearrange them in between scenarios. Needs fix?

  • Scenario duplication and selecting scenarios from the project also works from the tool view

Other

  • db_deepcopy recursive DB copy; duplicate_project / duplicate_scenario(_with_permissions) transfer group membership and area permissions to the copies
  • db_deepcopy has two slight changes in comparison to ebus version
    • copied objects can have a callback prepare_deepcopy, to take actions which make them deepcopieable. This is needed for objects which have constraints , e.g. a unique name/internal_id which can not be copied straight away
    • grids could not be deepcopied. The reason was a stale manager. The manager was created from an object which did not have the final database id. therefore the manager would call a sql query with this stale database id. this was fixed to recreate the manager from the instance on the manager which has the updated/fresh id
  foo.id == 100 123
  manager = foo.bar_set
  foo.id = 2
  # calling manager.add() would fail / try to use the old foo.id 100 123
  manager.instance.id == 2 # the instance is fresh though
  # update the manager
     manager = getattr(manager.instance, manager.prefetch_cache_name)
  the manager now works properly for the fresh instance
  • JSON ApiView for rename/duplicate/delete of projects and scenarios; has_authorization moved to ports/models.py and checked against the project
  • Projects overview page (cards/table, persisted search/sort/view) and project overview with its scenarios; ellipsis dropdowns + modals driving the JSON api (cotton api_button)
  • Extracted base_management shell with header/footer
  • Timezone middleware: tz cookie (browser IANA zone) activates the client timezone per request, all |date filters render local dates
  • Tests: test_duplicate_permissions.py (project/scenario/area permission transfer on duplication), extended test_detail_view.py

The splitting was done by claude. the diff together with #38 is identical to the original geometry_import
tests were generated by claude

Open TODOS for other PRs

Implement user rights / Nutzerverwaltung
Implement Szenarienvergleich
Implement scenario specific Ergebnisse

PaulScheerRLI and others added 6 commits July 7, 2026 11:22
Projects bundle scenarios; access is granted through a per-project
group with guardian object permissions ('view'/'details'). db_deepcopy
provides recursive DB-level copying used by duplicate_project and
duplicate_scenario(_with_permissions), which transfer group and
area permissions to the copies. The JSON ApiView handles rename,
delete and duplicate for projects and scenarios. has_authorization
moves from views to models and is checked against the project.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Projects page lists all accessible projects as cards or table rows
with persisted search/sort/view state. The project overview shows the
project's scenarios. Ellipsis dropdowns and modals drive rename,
duplicate and delete through the ports JSON api (cotton api_button
shows request state inline). Management pages share the extracted
base_management shell with header and footer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tz cookie (set by the script shipped with the overview pages'
imports.html) holds the browser's IANA zone name; the middleware
activates it per request so every |date filter renders local dates.
Missing or invalid cookies fall back to UTC.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base automatically changed from feature/geodata-import to dev August 3, 2026 13:05
@PaulScheerRLI
PaulScheerRLI requested a review from stefansc1 August 4, 2026 11:11
Comment thread core/templates/core/partials/create_project.html Outdated
<div class="grid grid-cols-1 gap-y-2 pt-2">
<div class="bg-[#f8fafc] content-stretch flex items-start relative shrink-0 w-full rounded-lg">
<div class="flex-[1_0_0] font-['Inter:Regular',sans-serif] font-normal min-h-px min-w-px not-italic relative text-[#1e293b] text-[14px] ">
<p class="leading-[23px] mb-0">{% translate 'Erstelle ein Projekt, um deine Energiezukunft zu planen:' %}</p>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General question: how formal/professional has this to be? I would suggest to put this as neutral as possible, e.g. "Neues Projekt erstellen, um Energiezukunft zu planen"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mosc5 opinion?

Comment thread core/templates/core/partials/create_project.html Outdated
Comment thread core/templates/core/partials/create_project.html Outdated
Comment thread core/templates/core/partials/create_project.html Outdated
Comment thread ports/util.py Outdated
Comment thread ports/views.py
Comment thread ports/views.py
Comment on lines +996 to +1003
def _check_permission(self, request):
is_authorized = False
match self.instance:
case Project():
is_authorized = has_authorization(self.instance, request.user, "details")
case Scenario():
is_authorized = has_authorization(self.instance.project, request.user, "details")
return is_authorized

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion:

def _check_permission(self, request):
  project = self.instance if isinstance(self.instance, Project) else self.instance.project
  return has_authorization(project, request.user, "details")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its likely the API will grow, and so will the permission check.

Comment thread ports/views.py
return is_authorized

def post(self, request, *args, **kwargs):
match self.instance:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could also be if isinstance instead of match case

Comment thread ports/views.py

def duplicate(self, request, *args, **kwargs):
try:
if self.Model == Project:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isinstance(self.instance) may be safer than relying on self.Model

@PaulScheerRLI PaulScheerRLI Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.instance was found using self.Model. Other methods are using pattern matching though. Maybe use the same pattern in every method? I like pattern matching since its slightly less redundant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants