From aa0670188a85db9e499b3caca444f34e7edc46af Mon Sep 17 00:00:00 2001 From: vincent porte Date: Thu, 12 Sep 2024 16:54:17 +0200 Subject: [PATCH] wip --- config/urls.py | 2 + lacommunaute/documentation/abstract_models.py | 8 +- lacommunaute/documentation/admin.py | 38 ++++++ .../commands/rearrange_documentation.py | 81 +++++++++++++ .../documentation/migrations/0003_document.py | 81 +++++++++++++ .../migrations/0004_documentrating.py | 41 +++++++ ...lter_documentrating_created_at_and_more.py | 23 ++++ lacommunaute/documentation/models.py | 40 ++++++- lacommunaute/documentation/urls.py | 13 ++ lacommunaute/documentation/views.py | 50 ++++++++ lacommunaute/forum/admin.py | 2 +- lacommunaute/forum/forms.py | 8 +- lacommunaute/forum/models.py | 15 ++- lacommunaute/forum/urls.py | 8 -- lacommunaute/forum/views.py | 111 +----------------- .../migrations/0009_topic_document.py | 26 ++++ lacommunaute/forum_conversation/models.py | 2 + lacommunaute/search/models.py | 4 + .../static/stylesheets/itou_communaute.scss | 6 + lacommunaute/stats/admin.py | 8 +- .../stats/migrations/0003_documentstat.py | 48 ++++++++ lacommunaute/stats/models.py | 24 ++++ lacommunaute/templates/404.html | 2 +- .../documentation/category_detail.html | 86 ++++++++++++++ .../documentation/category_list.html | 43 +++++++ .../documentation/document_detail.html | 48 ++++++++ .../documentation/partials/certified.html | 7 ++ .../partials/content_summary.html | 37 ++++++ .../partials/image_and_desc.html | 11 ++ .../documentation/partials/partner.html | 8 ++ .../partials/title_and_shortdesc.html | 11 ++ .../templates/forum/category_forum_list.html | 67 ----------- .../templates/forum/forum_documentation.html | 4 +- .../forum/forum_documentation_category.html | 17 --- .../templates/forum/partials/forum_form.html | 1 + lacommunaute/templates/pages/home.html | 2 +- .../templates/partials/ask_a_question.html | 2 +- .../templates/partials/breadcrumb.html | 37 ++++-- lacommunaute/templates/partials/footer.html | 2 +- lacommunaute/templates/partials/header.html | 2 +- .../partials/social_share_buttons.html | 2 +- 41 files changed, 799 insertions(+), 229 deletions(-) create mode 100644 lacommunaute/documentation/admin.py create mode 100644 lacommunaute/documentation/management/commands/rearrange_documentation.py create mode 100644 lacommunaute/documentation/migrations/0003_document.py create mode 100644 lacommunaute/documentation/migrations/0004_documentrating.py create mode 100644 lacommunaute/documentation/migrations/0005_alter_documentrating_created_at_and_more.py create mode 100644 lacommunaute/documentation/urls.py create mode 100644 lacommunaute/documentation/views.py create mode 100644 lacommunaute/forum_conversation/migrations/0009_topic_document.py create mode 100644 lacommunaute/stats/migrations/0003_documentstat.py create mode 100644 lacommunaute/templates/documentation/category_detail.html create mode 100644 lacommunaute/templates/documentation/category_list.html create mode 100644 lacommunaute/templates/documentation/document_detail.html create mode 100644 lacommunaute/templates/documentation/partials/certified.html create mode 100644 lacommunaute/templates/documentation/partials/content_summary.html create mode 100644 lacommunaute/templates/documentation/partials/image_and_desc.html create mode 100644 lacommunaute/templates/documentation/partials/partner.html create mode 100644 lacommunaute/templates/documentation/partials/title_and_shortdesc.html delete mode 100644 lacommunaute/templates/forum/category_forum_list.html delete mode 100644 lacommunaute/templates/forum/forum_documentation_category.html diff --git a/config/urls.py b/config/urls.py index 4dddbf67d..fe094e811 100644 --- a/config/urls.py +++ b/config/urls.py @@ -5,6 +5,7 @@ from django.urls import include, path, re_path from machina.core.loading import get_class +from lacommunaute.documentation import urls as documentation_urls from lacommunaute.event import urls as event_urls from lacommunaute.forum import urls as forum_extension_urls from lacommunaute.forum_conversation import urls as forum_conversation_extension_urls @@ -30,6 +31,7 @@ path("inclusion_connect/", include(inclusion_connect_urls)), # www. path("", include(pages_urls)), + path("documentation/", include(documentation_urls)), path("members/", include(forum_member_urls)), path("", include(forum_conversation_extension_urls)), path("", include(forum_extension_urls)), diff --git a/lacommunaute/documentation/abstract_models.py b/lacommunaute/documentation/abstract_models.py index 67e7e2419..a204fb7b1 100644 --- a/lacommunaute/documentation/abstract_models.py +++ b/lacommunaute/documentation/abstract_models.py @@ -9,9 +9,14 @@ from lacommunaute.utils.validators import validate_image_size -class AbstractPublication(models.Model): +class AbstractDatedModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + +class AbstractPublication(AbstractDatedModel): name = models.CharField(max_length=100, verbose_name=_("Name")) slug = models.SlugField(max_length=255, verbose_name=_("Slug"), unique=True) @@ -30,3 +35,4 @@ class Meta: def save(self, *args, **kwargs): self.slug = slugify(force_str(self.name), allow_unicode=True) super().save(*args, **kwargs) + diff --git a/lacommunaute/documentation/admin.py b/lacommunaute/documentation/admin.py new file mode 100644 index 000000000..be21ae80a --- /dev/null +++ b/lacommunaute/documentation/admin.py @@ -0,0 +1,38 @@ +from django.contrib import admin + +from lacommunaute.documentation.models import Category, Document, DocumentRating +from lacommunaute.forum.models import Forum + + +class DocumentInlines(admin.TabularInline): + model = Document + extra = 0 + fields = ("name", "short_description") + readonly_fields = ("name", "short_description") + + def has_delete_permission(self, request, obj=None): + return False + + def has_add_permission(self, request, obj=None): + return False + +@admin.register(Category) +class CategoryAdmin(admin.ModelAdmin): + list_display = ("name",) + search_fields = ("name",) + fields = ("name", "short_description", "description", "image") + inlines = [DocumentInlines] + +@admin.register(Document) +class DocumentAdmin(admin.ModelAdmin): + list_display = ("name","category") + list_filter = ("category",) + search_fields = ("name",) + fields = ("name", "short_description", "description", "image") + +@admin.register(DocumentRating) +class DocumentRatingAdmin(admin.ModelAdmin): + list_display = ("document", "rating", "created_at") + list_filter = ("document",) + list_display_links = ("rating",) + raw_id_fields = ("document", "user") diff --git a/lacommunaute/documentation/management/commands/rearrange_documentation.py b/lacommunaute/documentation/management/commands/rearrange_documentation.py new file mode 100644 index 000000000..19be358a2 --- /dev/null +++ b/lacommunaute/documentation/management/commands/rearrange_documentation.py @@ -0,0 +1,81 @@ + + +from django.contrib.contenttypes.models import ContentType +from taggit.models import TaggedItem + +from lacommunaute.documentation.models import Category, Document, DocumentRating +from lacommunaute.forum.models import Forum, ForumRating +from lacommunaute.forum_conversation.models import Topic +from lacommunaute.forum_upvote.models import UpVote +from lacommunaute.stats.models import DocumentStat, ForumStat + + +def create_categories_from_catforums(): + transpo_dict = {} + for forum in Forum.objects.filter(type=1, level=0): + category = Category.objects.create( + name=forum.name, + short_description=forum.short_description, + description=forum.description, + image=forum.image, + ) + print(f"{category} created") + transpo_dict[forum] = category + return transpo_dict + + +def create_document_from_forums(category_transpo_dict): + forum_content_type = ContentType.objects.get_for_model(Forum) + document_content_type = ContentType.objects.get_for_model(Document) + transpo_dict = {} + for forum in Forum.objects.filter(parent__type=1): + document = Document.objects.create( + name=forum.name, + short_description=forum.short_description, + description=forum.description, + image=forum.image, + category = category_transpo_dict[forum.parent], + partner = forum.partner, + certified = forum.certified, + ) + UpVote.objects.filter(content_type=forum_content_type, object_id=forum.id).update(content_type=document_content_type, object_id=document.id) + TaggedItem.objects.filter(content_type=forum_content_type, object_id=forum.id).update(content_type=document_content_type, object_id=document.id) + transpo_dict[forum] = document + print('Documents created') + return transpo_dict + +def migrate_ratings(document_transpo_dict): + document_ratings = [DocumentRating(document=document_transpo_dict[rating.forum], session_id=rating.session_id, rating=rating.rating, user=rating.user, created_at=rating.created, updated_at=rating.updated) for rating in ForumRating.objects.all()] + DocumentRating.objects.bulk_create(document_ratings) + ForumRating.objects.all().delete() + print(f"{len(document_ratings)} ratings migrated") + +def link_topics(document_transpo_dict): + main_forum = Forum.objects.get_main_forum() + for forum, document in document_transpo_dict.items(): + topics = Topic.objects.filter(forum=forum) + topics.update(document=document, forum=main_forum) + for topic in topics: + topic.save() + forum.save() + print('Topics linked to documents') + +def convert_stats(document_transpo_dict): + document_stats = [DocumentStat(document=document_transpo_dict[stat.forum], date=stat.date, period=stat.period, visits=stat.visits, entry_visits=stat.entry_visits, time_spent=stat.time_spent) for stat in ForumStat.objects.all()] + +def add_redirections(): + # for categories + # for documents + pass + +def del_forums(): + #categories + #documents + pass + + +# main +category_transpo_dict = create_categories_from_catforums() +document_transpo_dict = create_document_from_forums(category_transpo_dict) +migrate_ratings(document_transpo_dict) +link_topics(document_transpo_dict) diff --git a/lacommunaute/documentation/migrations/0003_document.py b/lacommunaute/documentation/migrations/0003_document.py new file mode 100644 index 000000000..3ea403a19 --- /dev/null +++ b/lacommunaute/documentation/migrations/0003_document.py @@ -0,0 +1,81 @@ +# Generated by Django 5.0.9 on 2024-09-11 13:48 + +import django.db.models.deletion +import lacommunaute.utils.validators +import machina.models.fields +import storages.backends.s3 +import taggit.managers +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("documentation", "0002_alter_category_options"), + ("partner", "0002_alter_partner_options"), + ("taggit", "0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx"), + ] + + operations = [ + migrations.CreateModel( + name="Document", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("name", models.CharField(max_length=100, verbose_name="Name")), + ("slug", models.SlugField(max_length=255, unique=True, verbose_name="Slug")), + ( + "description", + machina.models.fields.MarkupTextField( + blank=True, no_rendered_field=True, null=True, verbose_name="Description" + ), + ), + ( + "short_description", + models.CharField(blank=True, max_length=400, null=True, verbose_name="Description courte (SEO)"), + ), + ( + "image", + models.ImageField( + storage=storages.backends.s3.S3Storage(bucket_name="private-bucket", file_overwrite=False), + upload_to="", + validators=[lacommunaute.utils.validators.validate_image_size], + ), + ), + ( + "certified", + models.BooleanField(default=False, verbose_name="Certifié par la communauté de l'inclusion"), + ), + ("_description_rendered", models.TextField(blank=True, editable=False, null=True)), + ( + "category", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="documents", + to="documentation.category", + ), + ), + ( + "partner", + models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="partner.partner" + ), + ), + ( + "tags", + taggit.managers.TaggableManager( + help_text="A comma-separated list of tags.", + through="taggit.TaggedItem", + to="taggit.Tag", + verbose_name="Tags", + ), + ), + ], + options={ + "verbose_name": "Document", + "verbose_name_plural": "Documents", + "ordering": ["-created_at"], + }, + ), + ] diff --git a/lacommunaute/documentation/migrations/0004_documentrating.py b/lacommunaute/documentation/migrations/0004_documentrating.py new file mode 100644 index 000000000..0ae611ff4 --- /dev/null +++ b/lacommunaute/documentation/migrations/0004_documentrating.py @@ -0,0 +1,41 @@ +# Generated by Django 5.0.9 on 2024-09-12 14:14 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("documentation", "0003_document"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="DocumentRating", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("session_id", models.CharField(max_length=40)), + ("rating", models.PositiveSmallIntegerField()), + ( + "document", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="documentation.document"), + ), + ( + "user", + models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), + ), + ], + options={ + "verbose_name": "Notation d'un document", + "verbose_name_plural": "Notations des documents", + "ordering": ("-created_at",), + }, + ), + ] diff --git a/lacommunaute/documentation/migrations/0005_alter_documentrating_created_at_and_more.py b/lacommunaute/documentation/migrations/0005_alter_documentrating_created_at_and_more.py new file mode 100644 index 000000000..30f170828 --- /dev/null +++ b/lacommunaute/documentation/migrations/0005_alter_documentrating_created_at_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.9 on 2024-09-12 14:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("documentation", "0004_documentrating"), + ] + + operations = [ + migrations.AlterField( + model_name="documentrating", + name="created_at", + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name="documentrating", + name="updated_at", + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/lacommunaute/documentation/models.py b/lacommunaute/documentation/models.py index 91c2f5f4c..97dfbe769 100644 --- a/lacommunaute/documentation/models.py +++ b/lacommunaute/documentation/models.py @@ -1,4 +1,13 @@ -from lacommunaute.documentation.abstract_models import AbstractPublication +from pyexpat import model + +from django.conf import settings +from django.contrib.contenttypes.fields import GenericRelation +from django.db import models +from taggit.managers import TaggableManager + +from lacommunaute.documentation.abstract_models import AbstractDatedModel, AbstractPublication +from lacommunaute.forum_upvote.models import UpVote +from lacommunaute.partner.models import Partner class Category(AbstractPublication): @@ -9,3 +18,32 @@ class Meta: def __str__(self): return f"{self.name}" + +class Document(AbstractPublication): + category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="documents") + partner = models.ForeignKey(Partner, on_delete=models.CASCADE, null=True, blank=True) + upvotes = GenericRelation(UpVote, related_query_name="document") + certified = models.BooleanField(default=False, verbose_name="Certifié par la communauté de l'inclusion") + tags = TaggableManager() + + class Meta: + verbose_name = "Document" + verbose_name_plural = "Documents" + ordering = ["-created_at"] + + def __str__(self): + return f"{self.name}" + +# use AbstractDatedModel after ForumRanting migration +class DocumentRating(models.Model): + created_at = models.DateTimeField(null=True, blank=True) + updated_at = models.DateTimeField(null=True, blank=True) + session_id = models.CharField(max_length=40) + document = models.ForeignKey(Document, on_delete=models.CASCADE) + rating = models.PositiveSmallIntegerField() + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True) + + class Meta: + verbose_name = "Notation d'un document" + verbose_name_plural = "Notations des documents" + ordering = ("-created_at",) diff --git a/lacommunaute/documentation/urls.py b/lacommunaute/documentation/urls.py new file mode 100644 index 000000000..be6a40ddc --- /dev/null +++ b/lacommunaute/documentation/urls.py @@ -0,0 +1,13 @@ +from django.urls import path + +from lacommunaute.documentation.views import CategoryDetailView, CategoryListView, DocumentDetailView + + +app_name = "documentation" + + +urlpatterns = [ + path("", CategoryListView.as_view(), name="category_list"), + path("-/", CategoryDetailView.as_view(), name="category_detail"), + path("-/-/", DocumentDetailView.as_view(), name="document_detail"), +] diff --git a/lacommunaute/documentation/views.py b/lacommunaute/documentation/views.py new file mode 100644 index 000000000..a378e4a90 --- /dev/null +++ b/lacommunaute/documentation/views.py @@ -0,0 +1,50 @@ +from typing import Any + +from django.contrib.contenttypes.models import ContentType +from django.views.generic import DetailView, ListView +from taggit.models import Tag + +from lacommunaute.documentation.models import Category, Document +from lacommunaute.forum.models import Forum + + +class CategoryListView(ListView): + model = Category + template_name = "documentation/category_list.html" + context_object_name = "categories" + paginate_by = 20 * 3 + + +class CategoryDetailView(DetailView): + model = Category + template_name = "documentation/category_detail.html" + context_object_name = "category" + + def get_tags_of_documents(self): + return Tag.objects.filter( + taggit_taggeditem_items__content_type=ContentType.objects.get_for_model(Document), + taggit_taggeditem_items__object_id__in=self.object.documents.all().values_list("id", flat=True), + ).distinct() + + def get_queryset(self): + return super().get_queryset().prefetch_related("documents__tags") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["tags"] = self.get_tags_of_documents() + context["active_tag_slug"] = self.request.GET.get("tag") or None + return context + + + +# CategoryCreateUpdateView + + +class DocumentDetailView(DetailView): + model = Document + template_name = "documentation/document_detail.html" + context_object_name = "document" + + + +# DocumentCreateUpdateView diff --git a/lacommunaute/forum/admin.py b/lacommunaute/forum/admin.py index 21b624c63..37762ca71 100644 --- a/lacommunaute/forum/admin.py +++ b/lacommunaute/forum/admin.py @@ -6,7 +6,7 @@ class ForumAdmin(BaseForumAdmin): fieldsets = BaseForumAdmin.fieldsets - fieldsets[0][1]["fields"] += ("short_description", "certified", "tags", "partner") + fieldsets[0][1]["fields"] += ("short_description", "certified", "tags", "partner",) @admin.register(ForumRating) diff --git a/lacommunaute/forum/forms.py b/lacommunaute/forum/forms.py index c86796c65..7e60d751f 100644 --- a/lacommunaute/forum/forms.py +++ b/lacommunaute/forum/forms.py @@ -5,6 +5,7 @@ from django.forms import CharField, CheckboxSelectMultiple, ModelMultipleChoiceField from taggit.models import Tag +from lacommunaute.documentation.models import Category from lacommunaute.forum.models import Forum from lacommunaute.partner.models import Partner @@ -44,6 +45,11 @@ class ForumForm(forms.ModelForm): queryset=Partner.objects.all(), required=False, ) + category = forms.ModelChoiceField( + label="Sélectionner une catégorie documentaire", + queryset=Category.objects.all(), + required=False, + ) tags = ModelMultipleChoiceField( label="Sélectionner un ou plusieurs tags", queryset=Tag.objects.all(), @@ -73,4 +79,4 @@ def save(self, commit=True): class Meta: model = Forum - fields = ["name", "short_description", "description", "image", "certified", "partner"] + fields = ["name", "short_description", "description", "image", "certified", "partner", "category"] diff --git a/lacommunaute/forum/models.py b/lacommunaute/forum/models.py index 0f73b9baa..da03a6df8 100644 --- a/lacommunaute/forum/models.py +++ b/lacommunaute/forum/models.py @@ -20,20 +20,25 @@ def get_main_forum(self): class Forum(AbstractForum): + # to be removed after documentation refactor short_description = models.CharField( max_length=400, blank=True, null=True, verbose_name="Description courte (SEO)" ) + # to be removed after documentation refactor image = models.ImageField( storage=S3Boto3Storage(bucket_name=settings.AWS_STORAGE_BUCKET_NAME, file_overwrite=False), validators=[validate_image_size], ) + # to be removed after documentation refactor certified = models.BooleanField(default=False, verbose_name="Certifié par la communauté de l'inclusion") - + # to be removed after documentation refactor upvotes = GenericRelation(UpVote, related_query_name="forum") - + # to be removed after documentation refactor tags = TaggableManager() + # to be removed after documentation refactor partner = models.ForeignKey(Partner, on_delete=models.CASCADE, null=True, blank=True) + objects = ForumQuerySet().as_manager() def get_absolute_url(self): @@ -57,9 +62,7 @@ def upvotes_count(self): @cached_property def is_in_documentation_area(self): - return (self.type == Forum.FORUM_CAT and self.get_level() == 0) or ( - self.get_level() > 0 and self.get_ancestors().first().type == Forum.FORUM_CAT - ) + return self.category is not None @cached_property def is_toplevel_discussion_area(self): @@ -71,7 +74,7 @@ def get_session_rating(self, session_key): def get_average_rating(self): return ForumRating.objects.filter(forum=self).aggregate(models.Avg("rating"))["rating__avg"] - +# to be removed after documentation refactor class ForumRating(DatedModel): session_id = models.CharField(max_length=40) forum = models.ForeignKey(Forum, on_delete=models.CASCADE) diff --git a/lacommunaute/forum/urls.py b/lacommunaute/forum/urls.py index 77cd7c35c..4b29df8f2 100644 --- a/lacommunaute/forum/urls.py +++ b/lacommunaute/forum/urls.py @@ -2,13 +2,9 @@ from machina.apps.forum.views import IndexView from lacommunaute.forum.views import ( - CategoryForumCreateView, - CategoryForumListView, ForumRatingView, ForumUpdateView, ForumView, - SubCategoryForumCreateView, - SubCategoryForumListView, ) @@ -19,9 +15,5 @@ path("forum/-/", ForumView.as_view(), name="forum"), path("forum/-/update/", ForumUpdateView.as_view(), name="edit_forum"), path("forum/-/rate/", ForumRatingView.as_view(), name="rate"), - path("forum/-/subs/", SubCategoryForumListView.as_view(), name="subcategory_forums"), path("forums/", IndexView.as_view(), name="index"), - path("documentation/", CategoryForumListView.as_view(), name="documentation"), - path("documentation/category/create/", CategoryForumCreateView.as_view(), name="create_category"), - path("documentation/category//create/", SubCategoryForumCreateView.as_view(), name="create_subcategory"), ] diff --git a/lacommunaute/forum/views.py b/lacommunaute/forum/views.py index f16088f96..62526b7fd 100644 --- a/lacommunaute/forum/views.py +++ b/lacommunaute/forum/views.py @@ -3,21 +3,18 @@ from django.conf import settings from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.contenttypes.models import ContentType -from django.db.models.query import QuerySet from django.shortcuts import get_object_or_404, render -from django.urls import reverse, reverse_lazy +from django.urls import reverse from django.views import View -from django.views.generic import CreateView, ListView, UpdateView +from django.views.generic import UpdateView from machina.apps.forum.views import ForumView as BaseForumView from machina.core.loading import get_class -from taggit.models import Tag from lacommunaute.forum.forms import ForumForm from lacommunaute.forum.models import Forum, ForumRating from lacommunaute.forum_conversation.forms import PostForm from lacommunaute.forum_conversation.view_mixins import FilteredTopicsListViewMixin from lacommunaute.forum_upvote.models import UpVote -from lacommunaute.utils.perms import add_public_perms_on_forum, forum_visibility_content_tree_from_forums logger = logging.getLogger(__name__) @@ -25,32 +22,7 @@ PermissionRequiredMixin = get_class("forum_permission.viewmixins", "PermissionRequiredMixin") -class SubCategoryForumListMixin: - def get_descendants(self): - qs = self.get_forum().get_descendants() - - forum_tag = self.request.GET.get("forum_tag") or None - if forum_tag: - qs = qs.filter(tags__slug=forum_tag) - - return qs.prefetch_related("tags") - - def get_tags_of_descendants(self): - return Tag.objects.filter( - taggit_taggeditem_items__content_type=ContentType.objects.get_for_model(Forum), - taggit_taggeditem_items__object_id__in=self.get_forum().get_descendants().values_list("id", flat=True), - ).distinct() - - def forum_tag_context(self): - return { - # TODO : remove permission management, though all forums are public in our case - "sub_forums": forum_visibility_content_tree_from_forums(self.request, self.get_descendants()), - "tags_of_descendants": self.get_tags_of_descendants(), - "active_forum_tag_slug": self.request.GET.get("forum_tag") or None, - } - - -class ForumView(BaseForumView, FilteredTopicsListViewMixin, SubCategoryForumListMixin): +class ForumView(BaseForumView, FilteredTopicsListViewMixin): paginate_by = settings.FORUM_TOPICS_NUMBER_PER_PAGE def get_template_names(self): @@ -58,15 +30,10 @@ def get_template_names(self): return ["forum_conversation/topic_list.html"] if self.will_render_documentation_variant(): return ["forum/forum_documentation.html"] - if self.will_render_documentation_category_variant(): - return ["forum/forum_documentation_category.html"] return ["forum/forum_detail.html"] def will_render_documentation_variant(self): - return self.get_forum().parent and self.forum.is_in_documentation_area - - def will_render_documentation_category_variant(self): - return self.get_forum().is_in_documentation_area and self.forum.level == 0 + return self.forum.is_in_documentation_area def get_queryset(self): return self.filter_queryset(self.get_forum().topics.optimized_for_topics_list(self.request.user.id)) @@ -100,25 +67,14 @@ def get_context_data(self, **kwargs): ) context = context | self.get_topic_filter_context() - if self.will_render_documentation_category_variant(): - context = context | self.forum_tag_context() - if self.will_render_documentation_variant(): - context["sibling_forums"] = forum.get_siblings(include_self=True) + context["sibling_forums"] = Forum.objects.filter(category=forum.category).exclude(pk=forum.pk) if forum.image: context["og_image"] = forum.image return context -class SubCategoryForumListView(BaseForumView, SubCategoryForumListMixin): - template_name = "forum/partials/subcategory_forum_list.html" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) | self.forum_tag_context() - return context - - class ForumUpdateView(UserPassesTestMixin, UpdateView): template_name = "forum/forum_create_or_update.html" form_class = ForumForm @@ -134,63 +90,6 @@ def get_context_data(self, **kwargs): return context -class CategoryForumListView(ListView): - template_name = "forum/category_forum_list.html" - context_object_name = "forums" - - def get_queryset(self) -> QuerySet[Forum]: - return Forum.objects.filter(type=Forum.FORUM_CAT, level=0) - - -class BaseCategoryForumCreateView(UserPassesTestMixin, CreateView): - template_name = "forum/forum_create_or_update.html" - form_class = ForumForm - - def test_func(self): - return self.request.user.is_superuser - - def form_valid(self, form): - response = super().form_valid(form) - add_public_perms_on_forum(form.instance) - return response - - -class CategoryForumCreateView(BaseCategoryForumCreateView): - success_url = reverse_lazy("forum_extension:documentation") - - def form_valid(self, form): - form.instance.parent = None - form.instance.type = Forum.FORUM_CAT - return super().form_valid(form) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["title"] = "Créer une nouvelle catégorie documentaire" - context["back_url"] = reverse("forum_extension:documentation") - return context - - -class SubCategoryForumCreateView(BaseCategoryForumCreateView): - def get_success_url(self): - return reverse("forum_extension:forum", kwargs={"pk": self.object.pk, "slug": self.object.slug}) - - def get_parent_forum(self): - return Forum.objects.get(pk=self.kwargs["pk"]) - - def form_valid(self, form): - form.instance.type = Forum.FORUM_POST - form.instance.parent = self.get_parent_forum() - return super().form_valid(form) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["title"] = f"Créer une fiche pratique dans la catégorie {self.get_parent_forum().name}" - context["back_url"] = reverse( - "forum_extension:forum", kwargs={"pk": self.get_parent_forum().pk, "slug": self.get_parent_forum().slug} - ) - return context - - class ForumRatingView(View): def post(self, request, *args, **kwargs): forum_rating = ForumRating.objects.create( diff --git a/lacommunaute/forum_conversation/migrations/0009_topic_document.py b/lacommunaute/forum_conversation/migrations/0009_topic_document.py new file mode 100644 index 000000000..9c402b981 --- /dev/null +++ b/lacommunaute/forum_conversation/migrations/0009_topic_document.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.9 on 2024-09-11 13:48 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("documentation", "0003_document"), + ("forum_conversation", "0008_remove_topic_likers"), + ] + + operations = [ + migrations.AddField( + model_name="topic", + name="document", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="topics", + to="documentation.document", + ), + ), + ] diff --git a/lacommunaute/forum_conversation/models.py b/lacommunaute/forum_conversation/models.py index c0325e804..0d92706dc 100644 --- a/lacommunaute/forum_conversation/models.py +++ b/lacommunaute/forum_conversation/models.py @@ -9,6 +9,7 @@ from machina.models.abstract_models import DatedModel from taggit.managers import TaggableManager +from lacommunaute.documentation.models import Document from lacommunaute.forum_conversation.signals import post_create from lacommunaute.forum_member.shortcuts import get_forum_member_display_name from lacommunaute.forum_upvote.models import UpVote @@ -52,6 +53,7 @@ def optimized_for_topics_list(self, user_id): class Topic(AbstractTopic): tags = TaggableManager() + document = models.ForeignKey(Document, on_delete=models.SET_NULL, null=True, blank=True, related_name="topics") def get_absolute_url(self, with_fqdn=False): absolute_url = reverse( diff --git a/lacommunaute/search/models.py b/lacommunaute/search/models.py index a4c4840e4..3f0795d42 100644 --- a/lacommunaute/search/models.py +++ b/lacommunaute/search/models.py @@ -41,3 +41,7 @@ class CommonIndex(models.Model): class Meta: managed = False + + +# ajouter Document dans la materilized view +# ajouter Categorie dans la materilized view diff --git a/lacommunaute/static/stylesheets/itou_communaute.scss b/lacommunaute/static/stylesheets/itou_communaute.scss index bfb973771..25f2e44c9 100644 --- a/lacommunaute/static/stylesheets/itou_communaute.scss +++ b/lacommunaute/static/stylesheets/itou_communaute.scss @@ -227,3 +227,9 @@ span.highlighted { max-height: 100px; max-width: 150px; } + +.vertical-line { + border-left: 1px solid #a1a1a1; + padding-left: 10px; + height: 100%; +} diff --git a/lacommunaute/stats/admin.py b/lacommunaute/stats/admin.py index c802d823a..32056a8db 100644 --- a/lacommunaute/stats/admin.py +++ b/lacommunaute/stats/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from lacommunaute.forum.models import Forum -from lacommunaute.stats.models import ForumStat, Stat +from lacommunaute.stats.models import DocumentStat, ForumStat, Stat class ForumWithStatsFilter(admin.SimpleListFilter): @@ -43,3 +43,9 @@ class ForumStatAdmin(BaseStatAdmin): list_display = BaseStatAdmin.list_display + ("forum", "visits", "entry_visits", "time_spent") list_filter = BaseStatAdmin.list_filter + (ForumWithStatsFilter,) raw_id_fields = ("forum",) + +@admin.register(DocumentStat) +class DocumentStatAdmin(BaseStatAdmin): + list_display = BaseStatAdmin.list_display + ("document", "visits", "entry_visits", "time_spent") + list_filter = BaseStatAdmin.list_filter + ("document",) + raw_id_fields = ("document",) diff --git a/lacommunaute/stats/migrations/0003_documentstat.py b/lacommunaute/stats/migrations/0003_documentstat.py new file mode 100644 index 000000000..1a5b9b0c2 --- /dev/null +++ b/lacommunaute/stats/migrations/0003_documentstat.py @@ -0,0 +1,48 @@ +# Generated by Django 5.0.9 on 2024-09-12 14:45 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("documentation", "0005_alter_documentrating_created_at_and_more"), + ("stats", "0002_forumstat"), + ] + + operations = [ + migrations.CreateModel( + name="DocumentStat", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("date", models.DateField(verbose_name="Date")), + ( + "period", + models.CharField( + choices=[("month", "Month"), ("week", "Week"), ("day", "Day")], + max_length=10, + verbose_name="Période", + ), + ), + ("visits", models.IntegerField(default=0, verbose_name="Visites")), + ("entry_visits", models.IntegerField(default=0, verbose_name="Visites entrantes")), + ("time_spent", models.IntegerField(default=0, verbose_name="Temps passé")), + ( + "document", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="documentation.document", + verbose_name="Document", + ), + ), + ], + options={ + "verbose_name": "Stat d'un document", + "verbose_name_plural": "Stats des documents", + "ordering": ["date", "period", "document"], + "unique_together": {("date", "period", "document")}, + }, + ), + ] diff --git a/lacommunaute/stats/models.py b/lacommunaute/stats/models.py index 6d34d474c..6f1bfe1a5 100644 --- a/lacommunaute/stats/models.py +++ b/lacommunaute/stats/models.py @@ -1,5 +1,6 @@ from django.db import models +from lacommunaute.documentation.models import Document from lacommunaute.forum.models import Forum from lacommunaute.stats.enums import Period @@ -60,3 +61,26 @@ class Meta: def __str__(self): return f"{self.date} - {self.period} - {self.forum}" + +class DocumentStat(models.Model): + """ + Represents a statistical data point, relative to a forum, for a given date and period. + """ + + date = models.DateField(verbose_name="Date") + period = models.CharField(max_length=10, verbose_name="Période", choices=Period.choices) + document = models.ForeignKey(Document, on_delete=models.SET_NULL, verbose_name="Document", null=True) + visits = models.IntegerField(verbose_name="Visites", default=0) + entry_visits = models.IntegerField(verbose_name="Visites entrantes", default=0) + time_spent = models.IntegerField(verbose_name="Temps passé", default=0) + + objects = models.Manager() + + class Meta: + verbose_name = "Stat d'un document" + verbose_name_plural = "Stats des documents" + ordering = ["date", "period", "document"] + unique_together = ("date", "period", "document") + + def __str__(self): + return f"{self.date} - {self.period} - {self.document}" diff --git a/lacommunaute/templates/404.html b/lacommunaute/templates/404.html index b06262438..359bdb9dd 100644 --- a/lacommunaute/templates/404.html +++ b/lacommunaute/templates/404.html @@ -15,7 +15,7 @@

Bienvenue sur le site de la Communauté de l'Inclusion.

Espace d'échanges
  • - Documentation + Documentation
  • Recherche diff --git a/lacommunaute/templates/documentation/category_detail.html b/lacommunaute/templates/documentation/category_detail.html new file mode 100644 index 000000000..8a8164a7d --- /dev/null +++ b/lacommunaute/templates/documentation/category_detail.html @@ -0,0 +1,86 @@ +{% extends "layouts/base.html" %} +{% block title %}{{ category.name }}{{ block.super }}{% endblock %} +{% block meta_description %} + {{ category.short_description }} +{% endblock meta_description %} +{% block breadcrumb %} + {% include "partials/breadcrumb.html" %} +{% endblock %} +{% block content %} + {% load i18n %} + {% include 'documentation/partials/title_and_shortdesc.html' with obj=category user=user only %} + {% if category.description %} +
    +
    +
    {% include 'documentation/partials/image_and_desc.html' with obj=category only %}
    +
    +
    + {% endif %} + {% if tags %} +
    +
    +
    +
    +
    +
    Afficher les fiches contenant l'étiquette
    +
    + {% for tag in tags %} + {% if tag.slug == active_tag_slug %} + + {% else %} + + {% endif %} + {% endfor %} +
    +
    +
    +
    +
    +
    + {% endif %} +
    +
    +
    +
    +
    + {% for obj in category.documents.all %} + {% include 'documentation/partials/content_summary.html' with obj=obj kind='document' only %} + {% endfor %} +
    +
    +
    +
    +
    + {% if user.is_superuser %} +
    + +
    + {% endif %} +{% endblock content %} diff --git a/lacommunaute/templates/documentation/category_list.html b/lacommunaute/templates/documentation/category_list.html new file mode 100644 index 000000000..b3039af64 --- /dev/null +++ b/lacommunaute/templates/documentation/category_list.html @@ -0,0 +1,43 @@ +{% extends "layouts/base.html" %} +{% load i18n %} +{% block title %} + {% trans "Documents" %}{{ block.super }} +{% endblock %} +{% block meta_description %} + Des ressources exclusives pour les professionnels de l'inclusion. Retrouver les ressources nécessaires pour améliorer ses diagnostics socio-professionnels et ses accompagnements des personnes éloignées de l’emploi. +{% endblock meta_description %} +" +{% block content %} +
    +
    +
    +
    +

    {% trans "Documents" %}

    +

    + Retrouver les ressources nécessaires pour améliorer ses diagnostics socio-professionnels et ses accompagnements des personnes éloignées de l’emploi +

    +
    +
    +
    +
    +
    +
    +
    + {% for category in categories %} + {% include 'documentation/partials/content_summary.html' with obj=category kind='category' only %} + {% endfor %} +
    +
    +
    + {% if user.is_superuser %} +
    + +
    + {% endif %} +{% endblock content %} diff --git a/lacommunaute/templates/documentation/document_detail.html b/lacommunaute/templates/documentation/document_detail.html new file mode 100644 index 000000000..16ec10260 --- /dev/null +++ b/lacommunaute/templates/documentation/document_detail.html @@ -0,0 +1,48 @@ +{% extends "layouts/base.html" %} +{% block title %}{{ document.name }}{{ block.super }}{% endblock %} +{% block meta_description %} + {{ document.short_description }} +{% endblock meta_description %} +{% block breadcrumb %} + {% include "partials/breadcrumb.html" with document=document only %} +{% endblock %} +{% block content %} + {% load i18n %} + {% include 'documentation/partials/title_and_shortdesc.html' with obj=document user=user only %} + {% if document.description %} +
    +
    +
    +
    +
    +
    {% include 'documentation/partials/certified.html' with obj=document only %}
    +
    {% include "partials/upvotes.html" with obj=document %}
    + {% include 'documentation/partials/image_and_desc.html' with obj=document only %} + {% if document.partner %} +
    + {% include "documentation/partials/partner.html" with partner=document.partner only %} +
    + {% endif %} + {% comment %}{% include "forum/partials/rating.html" with forum=obj rating_area_id="1" %}{% endcomment %} +
    +
    +
    + Les autres fiches du thème {{ document.category.name }} + +
    +
    +
    +
    + {% endif %} +{% endblock content %} diff --git a/lacommunaute/templates/documentation/partials/certified.html b/lacommunaute/templates/documentation/partials/certified.html new file mode 100644 index 000000000..87d52279d --- /dev/null +++ b/lacommunaute/templates/documentation/partials/certified.html @@ -0,0 +1,7 @@ +{% if obj.certified %} + + + Certifiée par la communauté de l'inclusion + +{% endif %} +Mis à jour le {{ obj.updated_at|date:"d/m/Y" }} diff --git a/lacommunaute/templates/documentation/partials/content_summary.html b/lacommunaute/templates/documentation/partials/content_summary.html new file mode 100644 index 000000000..2f8037db7 --- /dev/null +++ b/lacommunaute/templates/documentation/partials/content_summary.html @@ -0,0 +1,37 @@ +
    + +
    diff --git a/lacommunaute/templates/documentation/partials/image_and_desc.html b/lacommunaute/templates/documentation/partials/image_and_desc.html new file mode 100644 index 000000000..0ed1b4fad --- /dev/null +++ b/lacommunaute/templates/documentation/partials/image_and_desc.html @@ -0,0 +1,11 @@ +{% load str_filters %} +{% if obj.image %} +
    +
    + {{ obj.name }} +
    +
    +{% endif %} +
    +
    {{ obj.description.rendered|urlizetrunc_target_blank:30|img_fluid }}
    +
    diff --git a/lacommunaute/templates/documentation/partials/partner.html b/lacommunaute/templates/documentation/partials/partner.html new file mode 100644 index 000000000..a40782717 --- /dev/null +++ b/lacommunaute/templates/documentation/partials/partner.html @@ -0,0 +1,8 @@ + +
    + {% if partner.logo %} + {{ partner.name }} + {% endif %} +

    Fiche co-rédigée en partenariat avec {{ partner.name }}

    +
    +
    diff --git a/lacommunaute/templates/documentation/partials/title_and_shortdesc.html b/lacommunaute/templates/documentation/partials/title_and_shortdesc.html new file mode 100644 index 000000000..863f2ec38 --- /dev/null +++ b/lacommunaute/templates/documentation/partials/title_and_shortdesc.html @@ -0,0 +1,11 @@ +
    +
    +
    +
    +

    {{ obj.name }}

    + {% if user.is_superuser %}Mettre à jour{% endif %} + {% if obj.short_description %}

    {{ obj.short_description }}

    {% endif %} +
    +
    +
    +
    diff --git a/lacommunaute/templates/forum/category_forum_list.html b/lacommunaute/templates/forum/category_forum_list.html deleted file mode 100644 index e1949d499..000000000 --- a/lacommunaute/templates/forum/category_forum_list.html +++ /dev/null @@ -1,67 +0,0 @@ -{% extends "layouts/base.html" %} -{% load i18n %} -{% block title %} - {% trans "Documents" %}{{ block.super }} -{% endblock %} -{% block meta_description %} - Des ressources exclusives pour les professionnels de l'inclusion. Retrouver les ressources nécessaires pour améliorer ses diagnostics socio-professionnels et ses accompagnements des personnes éloignées de l’emploi. -{% endblock meta_description %} -" -{% block content %} -
    -
    -
    -
    -

    {% trans "Documents" %}

    -

    - Retrouver les ressources nécessaires pour améliorer ses diagnostics socio-professionnels et ses accompagnements des personnes éloignées de l’emploi -

    -
    -
    -
    -
    -
    -
    -
    - {% for forum in forums %} -
    - -
    - {% endfor %} -
    -
    -
    - {% if user.is_superuser %} -
    - -
    - {% endif %} -{% endblock content %} diff --git a/lacommunaute/templates/forum/forum_documentation.html b/lacommunaute/templates/forum/forum_documentation.html index 070683850..038ca2a2d 100644 --- a/lacommunaute/templates/forum/forum_documentation.html +++ b/lacommunaute/templates/forum/forum_documentation.html @@ -50,11 +50,11 @@ {% endif %}
    - Les autres fiches du thème {{ forum.parent.name }} + data-matomo-option="forum">Les autres fiches du thème {{ forum.category.name }}