From 28b8b4ac236091000e9ab786b59ec2f9bc805c8b Mon Sep 17 00:00:00 2001 From: Antoni Czaplicki Date: Fri, 21 Feb 2025 22:34:27 +0100 Subject: [PATCH 01/10] feat: add wiki app [WiP] --- .idea/dataSources.xml | 12 -- eproba/apps/core/templates/core/base.html | 6 +- eproba/apps/core/templates/core/navbar.html | 3 + eproba/apps/teams/forms.py | 11 +- .../migrations/0006_team_organization.py | 21 +++ eproba/apps/teams/models.py | 12 ++ .../teams/templates/teams/team_request.html | 9 + eproba/apps/teams/views/team_request.py | 1 + eproba/apps/wiki/__init__.py | 0 eproba/apps/wiki/admin.py | 26 +++ eproba/apps/wiki/apps.py | 6 + eproba/apps/wiki/migrations/0001_initial.py | 177 ++++++++++++++++++ eproba/apps/wiki/migrations/__init__.py | 0 eproba/apps/wiki/models.py | 176 +++++++++++++++++ eproba/apps/wiki/templates/wiki/index.html | 145 ++++++++++++++ eproba/apps/wiki/templates/wiki/page.html | 20 ++ eproba/apps/wiki/urls.py | 12 ++ eproba/apps/wiki/views.py | 51 +++++ eproba/eproba/__init__.py | 2 +- eproba/eproba/settings.py | 16 ++ eproba/eproba/urls.py | 2 + eproba/requirements.txt | 6 +- 22 files changed, 694 insertions(+), 20 deletions(-) create mode 100644 eproba/apps/teams/migrations/0006_team_organization.py create mode 100644 eproba/apps/wiki/__init__.py create mode 100644 eproba/apps/wiki/admin.py create mode 100644 eproba/apps/wiki/apps.py create mode 100644 eproba/apps/wiki/migrations/0001_initial.py create mode 100644 eproba/apps/wiki/migrations/__init__.py create mode 100644 eproba/apps/wiki/models.py create mode 100644 eproba/apps/wiki/templates/wiki/index.html create mode 100644 eproba/apps/wiki/templates/wiki/page.html create mode 100644 eproba/apps/wiki/urls.py create mode 100644 eproba/apps/wiki/views.py diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 9120a984..c04a438c 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,18 +1,6 @@ - - sqlite.xerial - true - org.sqlite.JDBC - jdbc:sqlite:C:\Users\antek\PycharmProjects\eproba\eproba\db.sqlite3 - $ProjectFileDir$ - - - file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.34.0/sqlite-jdbc-3.34.0.jar - - - sqlite.xerial true diff --git a/eproba/apps/core/templates/core/base.html b/eproba/apps/core/templates/core/base.html index d51b9601..f2ba1883 100644 --- a/eproba/apps/core/templates/core/base.html +++ b/eproba/apps/core/templates/core/base.html @@ -87,10 +87,10 @@ right: 0; background: red; color: white; - padding: 10px 40px; + padding: 4px 50px; font-weight: bold; - font-size: 14px; - transform: rotate(45deg) translate(35px, -10px); + font-size: 16px; + transform: rotate(45deg) translate(43px, -10px); transform-origin: top right; z-index: 9999; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3); diff --git a/eproba/apps/core/templates/core/navbar.html b/eproba/apps/core/templates/core/navbar.html index a58b7949..063d7417 100644 --- a/eproba/apps/core/templates/core/navbar.html +++ b/eproba/apps/core/templates/core/navbar.html @@ -88,6 +88,9 @@ {% endif %} + + Wiki + +
+ +
+
+ {{ form.organization }} +
+
+
+
diff --git a/eproba/apps/teams/views/team_request.py b/eproba/apps/teams/views/team_request.py index 2db11780..5ce53782 100644 --- a/eproba/apps/teams/views/team_request.py +++ b/eproba/apps/teams/views/team_request.py @@ -32,6 +32,7 @@ def team_request(request): name=request.POST.get("team_name"), short_name=request.POST.get("team_short_name"), district=District.objects.get(id=request.POST.get("district")), + organization=int(request.POST.get("organization", 0)), is_verified=False, ) diff --git a/eproba/apps/wiki/__init__.py b/eproba/apps/wiki/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/eproba/apps/wiki/admin.py b/eproba/apps/wiki/admin.py new file mode 100644 index 00000000..8558750e --- /dev/null +++ b/eproba/apps/wiki/admin.py @@ -0,0 +1,26 @@ +from django.contrib import admin +from treebeard.admin import TreeAdmin +from treebeard.forms import movenodeform_factory + +from .models import Folder, Page + + +# Folder Admin with Tree Support +class FolderAdmin(TreeAdmin): + form = movenodeform_factory(Folder) + list_display = ("name", "owner", "visible", "created_at", "updated_at") + list_filter = ("visible", "created_at", "updated_at") + search_fields = ("name",) + + +# Page Admin +@admin.register(Page) +class PageAdmin(admin.ModelAdmin): + list_display = ("title", "folder", "owner", "visible", "created_at", "updated_at") + list_filter = ("visible", "created_at", "updated_at", "folder") + search_fields = ("title", "content") + ordering = ("title",) + + +# Register Folder with TreeAdmin +admin.site.register(Folder, FolderAdmin) diff --git a/eproba/apps/wiki/apps.py b/eproba/apps/wiki/apps.py new file mode 100644 index 00000000..48569e55 --- /dev/null +++ b/eproba/apps/wiki/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class WikiConfig(AppConfig): + name = "apps.wiki" + verbose_name = "Wiki" diff --git a/eproba/apps/wiki/migrations/0001_initial.py b/eproba/apps/wiki/migrations/0001_initial.py new file mode 100644 index 00000000..937d9703 --- /dev/null +++ b/eproba/apps/wiki/migrations/0001_initial.py @@ -0,0 +1,177 @@ +# Generated by Django 5.1.6 on 2025-02-21 21:15 + +import uuid + +import django.db.models.deletion +import tinymce.models +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("teams", "0006_team_organization"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Folder", + fields=[ + ("path", models.CharField(max_length=255, unique=True)), + ("depth", models.PositiveIntegerField()), + ("numchild", models.PositiveIntegerField(default=0)), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=255)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("visible", models.BooleanField(default=True)), + ("inherit_permissions", models.BooleanField(default=True)), + ("for_all", models.BooleanField(default=False)), + ( + "organization", + models.IntegerField( + blank=True, + choices=[ + (0, "Organizacja Harcerzy"), + (1, "Organizacja Harcerek"), + ], + null=True, + ), + ), + ( + "district", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="teams.district", + ), + ), + ( + "owner", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "patrol", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="teams.patrol", + ), + ), + ( + "team", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="teams.team", + ), + ), + ], + options={ + "verbose_name": "Folder", + "verbose_name_plural": "Folders", + }, + ), + migrations.CreateModel( + name="Page", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("title", models.CharField(max_length=255)), + ("content", tinymce.models.HTMLField()), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("visible", models.BooleanField(default=True)), + ("inherit_permissions", models.BooleanField(default=True)), + ("for_all", models.BooleanField(default=False)), + ( + "organization", + models.IntegerField( + blank=True, + choices=[ + (0, "Organizacja Harcerzy"), + (1, "Organizacja Harcerek"), + ], + null=True, + ), + ), + ( + "district", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="teams.district", + ), + ), + ( + "folder", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="pages", + to="wiki.folder", + ), + ), + ( + "owner", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "patrol", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="teams.patrol", + ), + ), + ( + "team", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="teams.team", + ), + ), + ], + options={ + "verbose_name": "Page", + "verbose_name_plural": "Pages", + }, + ), + ] diff --git a/eproba/apps/wiki/migrations/__init__.py b/eproba/apps/wiki/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/eproba/apps/wiki/models.py b/eproba/apps/wiki/models.py new file mode 100644 index 00000000..5e7916de --- /dev/null +++ b/eproba/apps/wiki/models.py @@ -0,0 +1,176 @@ +import uuid + +from django.db import models +from django.db.models import UUIDField +from django.urls import reverse +from tinymce.models import HTMLField +from treebeard.mp_tree import MP_Node + +from ..teams.models import District, OrganizationChoice, Patrol, Team +from ..users.models import User + +MAX_DEPTH = 6 + + +class Folder(MP_Node): + id = UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField(max_length=255) + owner = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + visible = models.BooleanField(default=True) + + inherit_permissions = models.BooleanField(default=True) + for_all = models.BooleanField(default=False) + organization = models.IntegerField( + choices=OrganizationChoice.choices, null=True, blank=True + ) + district = models.ForeignKey( + District, null=True, blank=True, on_delete=models.CASCADE + ) + team = models.ForeignKey(Team, null=True, blank=True, on_delete=models.CASCADE) + patrol = models.ForeignKey(Patrol, null=True, blank=True, on_delete=models.CASCADE) + + node_order_by = ["name"] # Automatically orders nodes alphabetically + + class Meta: + verbose_name = "Folder" + verbose_name_plural = "Folders" + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("wiki:folder", kwargs={"pk": self.pk}) + + # Optimized permission check + def can_view(self, user: User, for_list=False): + if self.for_all or user.is_superuser or self.owner == user: + return not for_list + + user_patrol = getattr(user, "patrol", None) + user_team = getattr(user_patrol, "team", None) if user_patrol else None + + # Check this folder + if ( + ( + self.organization + and user_team + and user_team.organization == self.organization + ) + or (self.district and user_team and user_team.district == self.district) + or (self.team and user_team and user_team == self.team) + or (self.patrol and user_patrol and user_patrol == self.patrol) + ): + return True + + # Check all its parent folders + if self.inherit_permissions: + for folder in self.get_ancestors(): + if ( + ( + folder.organization + and user_team + and user_team.organization == folder.organization + ) + or ( + folder.district + and user_team + and user_team.district == folder.district + ) + or (folder.team and user_team and user_team == folder.team) + or (folder.patrol and user_patrol and user_patrol == folder.patrol) + ): + return True + + return False + + # Optimized edit permission check + def can_edit(self, user: User): + if user.is_superuser or self.owner == user: + return True + + # Check the entire hierarchy for edit permission + for folder in self.get_ancestors(): + if folder.owner == user: + return True + + return False + + def get_pages(self): + return self.pages.all() + + def get_content(self): + return self.get_children(), self.get_pages() + + def get_content_count(self): + return self.get_children().count(), self.get_pages().count() + + def get_content_count_combined(self): + return self.get_children().count() + self.get_pages().count() + + def is_empty(self): + return not self.get_content_count_combined() + + def get_path(self): + return self.get_ancestors() + + +class Page(models.Model): + id = UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + title = models.CharField(max_length=255) + content = HTMLField() + folder = models.ForeignKey( + Folder, on_delete=models.CASCADE, related_name="pages", null=True, blank=True + ) + owner = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + visible = models.BooleanField(default=True) + + inherit_permissions = models.BooleanField(default=True) + for_all = models.BooleanField(default=False) + organization = models.IntegerField( + choices=OrganizationChoice.choices, null=True, blank=True + ) + district = models.ForeignKey( + District, null=True, blank=True, on_delete=models.CASCADE + ) + team = models.ForeignKey(Team, null=True, blank=True, on_delete=models.CASCADE) + patrol = models.ForeignKey(Patrol, null=True, blank=True, on_delete=models.CASCADE) + + class Meta: + verbose_name = "Page" + verbose_name_plural = "Pages" + + def __str__(self): + return self.title + + def get_absolute_url(self): + return reverse("wiki:page", kwargs={"pk": self.pk}) + + def can_view(self, user: User): + if self.for_all or user.is_superuser or self.owner == user: + return True + + user_patrol = getattr(user, "patrol", None) + user_team = getattr(user_patrol, "team", None) if user_patrol else None + + return ( + ( + self.organization + and user_team + and user_team.organization == self.organization + ) + or (self.district and user_team and user_team.district == self.district) + or (self.team and user_team and user_team == self.team) + or (self.patrol and user_patrol and user_patrol == self.patrol) + or (self.folder and self.inherit_permissions and self.folder.can_view(user)) + ) + + def can_edit(self, user: User): + if user.is_superuser or self.owner == user: + return True + if self.folder: + return self.folder.can_edit(user) + return False diff --git a/eproba/apps/wiki/templates/wiki/index.html b/eproba/apps/wiki/templates/wiki/index.html new file mode 100644 index 00000000..bb174626 --- /dev/null +++ b/eproba/apps/wiki/templates/wiki/index.html @@ -0,0 +1,145 @@ +{% extends "core/base.html" %} +{% block content %} + + {% if folder %} +

{{ folder.name }}

+ {% endif %} +
+ {% for folder in folders %} +
+
+ {% if folder.is_empty %} + + {% else %} + + {% endif %} +
+
+

{{ folder.name }}

+

+ {% if not folder.is_empty %}{{ folder.get_content_count_combined }} w + środku{% else %}Pusty{% endif %}

+
+
+ {% for child in folder.get_children.all %} +
+
+ {% if child.is_empty %} + + {% else %} + + {% endif %} +
+
+

{{ child.name }}

+

+ {% if not child.is_empty %}{{ child.get_content_count_combined }} w + środku{% else %}Pusty{% endif %}

+
+
+ {% endfor %} + {% for page in folder.pages.all %} +
+
+ +
+
+

{{ page.title }}

+

{{ page.created_at }}

+
+
+ {% endfor %} + {% endfor %} + {% for page in pages %} +
+
+ +
+
+

{{ page.title }}

+

{{ page.created_at }}

+
+
+ {% endfor %} + {% if not folders and not pages %} +

Brak zawartości

+ {% endif %} +
+ +{% endblock %} diff --git a/eproba/apps/wiki/templates/wiki/page.html b/eproba/apps/wiki/templates/wiki/page.html new file mode 100644 index 00000000..e4083717 --- /dev/null +++ b/eproba/apps/wiki/templates/wiki/page.html @@ -0,0 +1,20 @@ +{% extends "core/base.html" %} +{% block content %} + +

{{ page.title }}

+
+
{{ page.content | safe }} +
+
+{% endblock %} diff --git a/eproba/apps/wiki/urls.py b/eproba/apps/wiki/urls.py new file mode 100644 index 00000000..6ef4aac0 --- /dev/null +++ b/eproba/apps/wiki/urls.py @@ -0,0 +1,12 @@ +from django.urls import path + +from . import views + +app_name = "wiki" +urlpatterns = [ + path("", views.folder, name="index"), + path("folder//", views.folder, name="folder"), + path("page//", views.page, name="page"), + path("create-page/", views.folder, name="create_root_page"), + path("create-page//", views.folder, name="create_page"), +] diff --git a/eproba/apps/wiki/views.py b/eproba/apps/wiki/views.py new file mode 100644 index 00000000..37e3e66b --- /dev/null +++ b/eproba/apps/wiki/views.py @@ -0,0 +1,51 @@ +from django.contrib.auth.decorators import login_required +from django.db.models import Q +from django.shortcuts import get_object_or_404, render + +from .models import Folder, Page + + +@login_required +def folder(request, folder_id=None): + if folder_id: + folder_obj = get_object_or_404(Folder, id=folder_id) + folders = [ + _folder + for _folder in folder_obj.get_children() + if _folder.can_view(request.user) + ] + pages = [ + _page for _page in folder_obj.get_pages() if _page.can_view(request.user) + ] + path = folder_obj.get_ancestors() # Get breadcrumb path + return render( + request, + "wiki/index.html", + {"folders": folders, "pages": pages, "path": path, "folder": folder_obj}, + ) + + # Get root-level folders (those without a parent) + folders = Folder.get_root_nodes().filter( + Q(owner=request.user) + | Q(for_all=True) + | Q(team=request.user.patrol.team) + | Q(patrol=request.user.patrol) + | Q(organization=request.user.patrol.team.organization) + | Q(district=request.user.patrol.team.district) + ) + pages = Page.objects.filter(folder=None).filter( + Q(owner=request.user) + | Q(for_all=True) + | Q(team=request.user.patrol.team) + | Q(patrol=request.user.patrol) + | Q(organization=request.user.patrol.team.organization) + | Q(district=request.user.patrol.team.district) + ) + + return render(request, "wiki/index.html", {"folders": folders, "pages": pages}) + + +@login_required +def page(request, page_id): + page = get_object_or_404(Page, id=page_id) + return render(request, "wiki/page.html", {"page": page}) diff --git a/eproba/eproba/__init__.py b/eproba/eproba/__init__.py index 0d75d663..1ea4afcf 100644 --- a/eproba/eproba/__init__.py +++ b/eproba/eproba/__init__.py @@ -1 +1 @@ -__version__ = "2025.02.18" +__version__ = "2025.02.21" diff --git a/eproba/eproba/settings.py b/eproba/eproba/settings.py index 756b2985..1a610605 100644 --- a/eproba/eproba/settings.py +++ b/eproba/eproba/settings.py @@ -62,6 +62,7 @@ "apps.worksheets.apps.WorksheetConfig", "apps.users.apps.UsersConfig", "apps.teams.apps.TeamsConfig", + "apps.wiki.apps.WikiConfig", "crispy_forms", "crispy_bulma", "fcm_django", @@ -70,6 +71,8 @@ "maintenance_mode", "drf_spectacular", "dbbackup", + "tinymce", + "treebeard", ] # Middleware @@ -266,3 +269,16 @@ DBBACKUP_STORAGE_OPTIONS = { "location": os.environ.get("DBBACKUP_STORAGE_LOCATION", BASE_DIR / "backups"), } + +# TinyMCE +TINYMCE_DEFAULT_CONFIG = { + "promotion": False, + "menubar": "file edit view insert format tools table help", + "plugins": "advlist autolink lists link image charmap preview anchor searchreplace visualblocks code " + "fullscreen insertdatetime media table paste code help wordcount", + "toolbar": "undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft " + "aligncenter alignright alignjustify | outdent indent | numlist bullist checklist | forecolor " + "backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | " + "fullscreen preview save | insertfile image media pageembed template link anchor codesample | " + "a11ycheck ltr rtl | showcomments addcomment code", +} diff --git a/eproba/eproba/urls.py b/eproba/eproba/urls.py index 1fd4c037..68054707 100644 --- a/eproba/eproba/urls.py +++ b/eproba/eproba/urls.py @@ -208,4 +208,6 @@ SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui", ), + path("tinymce/", include("tinymce.urls")), + path("wiki/", include("apps.wiki.urls")), ] diff --git a/eproba/requirements.txt b/eproba/requirements.txt index 40196f8c..4aa7b9b4 100644 --- a/eproba/requirements.txt +++ b/eproba/requirements.txt @@ -4,7 +4,7 @@ crispy-bulma==0.11.0 djangorestframework==3.15.2 whitenoise==6.9.0 unidecode==1.3.8 -weasyprint==64.0 +weasyprint==64.1 fcm-django==2.2.1 gunicorn==23.0.0 django-oauth-toolkit==3.0.1 @@ -17,4 +17,6 @@ google-api-python-client==2.161.0 python-dotenv==1.0.1 psycopg[binary]==3.2.4 drf-spectacular==0.28.0 -django-dbbackup==4.2.1 \ No newline at end of file +django-dbbackup==4.2.1 +django-treebeard==4.7.1 +django-tinymce==4.1.0 \ No newline at end of file From 908f620f05456813aab373d654a506e2ac9af19c Mon Sep 17 00:00:00 2001 From: Antoni Czaplicki Date: Tue, 25 Feb 2025 10:42:43 +0100 Subject: [PATCH 02/10] feat: improve wiki app [WiP] --- eproba/apps/users/models.py | 16 +- eproba/apps/wiki/forms.py | 19 ++ eproba/apps/wiki/models.py | 38 +++- .../apps/wiki/templates/wiki/create_page.html | 70 ++++++ .../apps/wiki/templates/wiki/edit_page.html | 73 +++++++ eproba/apps/wiki/templates/wiki/index.html | 205 +++++++++++------- eproba/apps/wiki/templates/wiki/page.html | 37 +++- eproba/apps/wiki/urls.py | 13 +- eproba/apps/wiki/views.py | 169 ++++++++++++--- eproba/eproba/__init__.py | 2 +- eproba/eproba/settings.py | 24 +- eproba/requirements.txt | 2 +- eproba/static/js/init_tinymce.js | 82 +++++++ 13 files changed, 614 insertions(+), 136 deletions(-) create mode 100644 eproba/apps/wiki/forms.py create mode 100644 eproba/apps/wiki/templates/wiki/create_page.html create mode 100644 eproba/apps/wiki/templates/wiki/edit_page.html create mode 100644 eproba/static/js/init_tinymce.js diff --git a/eproba/apps/users/models.py b/eproba/apps/users/models.py index 5fc81eb2..9b8f4e27 100644 --- a/eproba/apps/users/models.py +++ b/eproba/apps/users/models.py @@ -120,6 +120,10 @@ def rank(self): else "" ) + _gender = ( + self.patrol.team.organization if self.patrol is not None else self.gender + ) + scout_rank_male = { 1: "biszkopt", 2: "mł.", @@ -138,9 +142,9 @@ def rank(self): 6: "HR", } - if self.gender == 0: + if _gender == 0: scout_rank = scout_rank_male.get(self.scout_rank, "") - elif self.gender == 1: + elif _gender == 1: scout_rank = scout_rank_female.get(self.scout_rank, "") else: scout_rank = self.get_scout_rank_display() @@ -149,6 +153,10 @@ def rank(self): def full_rank(self): + _gender = ( + self.patrol.team.organization if self.patrol is not None else self.gender + ) + instructor_rank_male = {1: "przewodnik", 2: "podharcmistrz", 3: "harcmistrz"} instructor_rank_female = { @@ -175,10 +183,10 @@ def full_rank(self): 6: "harcerka Rzeczypospolitej", } - if self.gender == 0: + if _gender == 0: instructor_rank = instructor_rank_male.get(self.instructor_rank, "") scout_rank = scout_rank_male.get(self.scout_rank, "") - elif self.gender == 1: + elif _gender == 1: instructor_rank = instructor_rank_female.get(self.instructor_rank, "") scout_rank = scout_rank_female.get(self.scout_rank, "") else: diff --git a/eproba/apps/wiki/forms.py b/eproba/apps/wiki/forms.py new file mode 100644 index 00000000..99d44719 --- /dev/null +++ b/eproba/apps/wiki/forms.py @@ -0,0 +1,19 @@ +# wiki/forms.py +from django import forms +from tinymce.widgets import TinyMCE + +from .models import Page + + +class PageForm(forms.ModelForm): + class Meta: + model = Page + fields = ["title", "content"] + widgets = { + "title": forms.TextInput( + attrs={"class": "input", "placeholder": "Wprowadź tytuł strony"} + ), + "content": TinyMCE( + attrs={"class": "textarea", "placeholder": "Wprowadź treść strony"} + ), + } diff --git a/eproba/apps/wiki/models.py b/eproba/apps/wiki/models.py index 5e7916de..fb6b847e 100644 --- a/eproba/apps/wiki/models.py +++ b/eproba/apps/wiki/models.py @@ -9,8 +9,6 @@ from ..teams.models import District, OrganizationChoice, Patrol, Team from ..users.models import User -MAX_DEPTH = 6 - class Folder(MP_Node): id = UUIDField(primary_key=True, default=uuid.uuid4, editable=False) @@ -45,7 +43,12 @@ def get_absolute_url(self): # Optimized permission check def can_view(self, user: User, for_list=False): - if self.for_all or user.is_superuser or self.owner == user: + if ( + self.for_all + or self.owner == user + or user.is_staff + and user.has_perm("wiki.view_folder") + ): return not for_list user_patrol = getattr(user, "patrol", None) @@ -87,14 +90,26 @@ def can_view(self, user: User, for_list=False): # Optimized edit permission check def can_edit(self, user: User): - if user.is_superuser or self.owner == user: + if self.owner == user or user.is_staff and user.has_perm("wiki.change_folder"): return True - # Check the entire hierarchy for edit permission - for folder in self.get_ancestors(): - if folder.owner == user: + if self.team and user.patrol and user.patrol.team == self.team: + if user.function >= 3: + return True + elif user.function >= 2 and self.patrol == user.patrol: return True + # Check the entire hierarchy for edit permission + if self.inherit_permissions: + for folder in self.get_ancestors(): + if folder.owner == user: + return True + if folder.team and user.patrol and user.patrol.team == folder.team: + if user.function >= 3: + return True + elif user.function >= 2 and folder.patrol == user.patrol: + return True + return False def get_pages(self): @@ -150,7 +165,12 @@ def get_absolute_url(self): return reverse("wiki:page", kwargs={"pk": self.pk}) def can_view(self, user: User): - if self.for_all or user.is_superuser or self.owner == user: + if ( + self.for_all + or self.owner == user + or user.is_staff + and user.has_perm("wiki.view_page") + ): return True user_patrol = getattr(user, "patrol", None) @@ -169,7 +189,7 @@ def can_view(self, user: User): ) def can_edit(self, user: User): - if user.is_superuser or self.owner == user: + if self.owner == user or user.is_staff and user.has_perm("wiki.change_page"): return True if self.folder: return self.folder.can_edit(user) diff --git a/eproba/apps/wiki/templates/wiki/create_page.html b/eproba/apps/wiki/templates/wiki/create_page.html new file mode 100644 index 00000000..7e739160 --- /dev/null +++ b/eproba/apps/wiki/templates/wiki/create_page.html @@ -0,0 +1,70 @@ +{% extends "core/base.html" %} +{% load static %} +{% block extrahead %} + + +{% endblock %} +{% block content %} + +

Stwórz stronę

+
+ {% csrf_token %} + {% if folder %} +
+ +
+ +
+
+ {% endif %} + +
+ +
+ {{ form.title }} +
+ {% if form.title.errors %} +

{{ form.title.errors }}

+ {% endif %} +
+ +
+ +
+ {{ form.content }} +
+ {% if form.content.errors %} +

{{ form.content.errors }}

+ {% endif %} +
+ +
+
+ +
+
+ Anuluj +
+
+
+{% endblock %} \ No newline at end of file diff --git a/eproba/apps/wiki/templates/wiki/edit_page.html b/eproba/apps/wiki/templates/wiki/edit_page.html new file mode 100644 index 00000000..bb3af854 --- /dev/null +++ b/eproba/apps/wiki/templates/wiki/edit_page.html @@ -0,0 +1,73 @@ +{% extends "core/base.html" %} +{% load static %} +{% block extrahead %} + + +{% endblock %} +{% block content %} + +

Edytuj stronę

+
+ {% csrf_token %} + {% if folder %} +
+ +
+ +
+
+ {% endif %} + +
+ +
+ {{ form.title }} +
+ {% if form.title.errors %} +

{{ form.title.errors }}

+ {% endif %} +
+ +
+ +
+ {{ form.content }} +
+ {% if form.content.errors %} +

{{ form.content.errors }}

+ {% endif %} +
+ +
+
+ +
+
+ Anuluj +
+
+
+{% endblock %} \ No newline at end of file diff --git a/eproba/apps/wiki/templates/wiki/index.html b/eproba/apps/wiki/templates/wiki/index.html index bb174626..92aa89d2 100644 --- a/eproba/apps/wiki/templates/wiki/index.html +++ b/eproba/apps/wiki/templates/wiki/index.html @@ -1,42 +1,56 @@ {% extends "core/base.html" %} + {% block content %} + - {% if folder %} -

{{ folder.name }}

+ + {% if current_folder %} +

{{ current_folder.name }}

{% endif %} + +
{% for folder in folders %} -
+
{% if folder.is_empty %} @@ -47,13 +61,17 @@

{{ folder.name }}

{{ folder.name }}

- {% if not folder.is_empty %}{{ folder.get_content_count_combined }} w - środku{% else %}Pusty{% endif %}

+ {% if not folder.is_empty %} + {{ folder.get_content_count_combined }} w środku + {% else %} + Pusty + {% endif %} +

+ {% for child in folder.get_children.all %} -
+
{% if child.is_empty %} @@ -64,14 +82,18 @@

{{ folder.name }}

{{ child.name }}

- {% if not child.is_empty %}{{ child.get_content_count_combined }} w - środku{% else %}Pusty{% endif %}

+ {% if not child.is_empty %} + {{ child.get_content_count_combined }} w środku + {% else %} + Pusty + {% endif %} +

{% endfor %} + {% for page in folder.pages.all %} -
+
@@ -82,9 +104,9 @@

{{ folder.name }}

{% endfor %} {% endfor %} + {% for page in pages %} -
+
@@ -94,52 +116,89 @@

{{ folder.name }}

{% endfor %} + {% if not folders and not pages %}

Brak zawartości

{% endif %} + + {% if allow_wiki_init %} +
+ +

Utwórz wiki dla swojej drużyny

+
+ {% endif %}
- + {% endif %} + + + {% if allow_edit and current_folder %} +
-
-{% endblock %} + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/eproba/apps/wiki/templates/wiki/page.html b/eproba/apps/wiki/templates/wiki/page.html index e4083717..23b258ad 100644 --- a/eproba/apps/wiki/templates/wiki/page.html +++ b/eproba/apps/wiki/templates/wiki/page.html @@ -1,16 +1,31 @@ {% extends "core/base.html" %} {% block content %} -