diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99fe2e0..fd66db1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ default_language_version: - python: python3.12 + python: python3 repos: # Using this mirror lets us use mypyc-compiled black, which is about 2x faster - repo: https://github.com/psf/black-pre-commit-mirror diff --git a/src/agenda_culturel/urls.py b/src/agenda_culturel/urls.py index 1eee369..27174b2 100644 --- a/src/agenda_culturel/urls.py +++ b/src/agenda_culturel/urls.py @@ -17,25 +17,31 @@ from .sitemaps import ( ) from .models import Event, Place, Organisation, Category from .views import ( -# Errors + # Errors internal_server_error, page_not_found, -# General pages + # General pages about, import_requirements, mentions_legales, moderation_rules, thank_you, -# TODO pas encore triƩ + # tags + view_tag, + view_tag_past, + TagUpdateView, + tag_list, + TagDeleteView, + rename_tag, + delete_tag, + TagCreateView, + # TODO pas encore triƩ home, week_view, month_view, day_view, upcoming_events, export_ical, - view_tag, - view_tag_past, - TagUpdateView, recent, administration, activite, @@ -64,11 +70,6 @@ from .views import ( cancel_import, run_all_fb_rimports, run_all_rimports, - tag_list, - TagDeleteView, - rename_tag, - delete_tag, - TagCreateView, EventDetailView, EventUpdateView, EventModerateView, diff --git a/src/agenda_culturel/views/__init__.py b/src/agenda_culturel/views/__init__.py index 5bfa51d..3d380be 100644 --- a/src/agenda_culturel/views/__init__.py +++ b/src/agenda_culturel/views/__init__.py @@ -1,3 +1,4 @@ from .oldviews import * from .errors import * -from .general_pages_views import * \ No newline at end of file +from .general_pages_views import * +from .tag_views import * diff --git a/src/agenda_culturel/views/oldviews.py b/src/agenda_culturel/views/oldviews.py index eb9ebfa..1ebaa5b 100644 --- a/src/agenda_culturel/views/oldviews.py +++ b/src/agenda_culturel/views/oldviews.py @@ -79,8 +79,6 @@ from ..forms import ( RecurrentImportForm, SelectEventInList, SimpleContactForm, - TagForm, - TagRenameForm, URLSubmissionFormSet, URLSubmissionFormWithContact, UserProfileForm, @@ -97,11 +95,11 @@ from ..models import ( Place, RecurrentImport, StaticContent, - Tag, remove_accents, UserProfile, ) from ..utils import PlaceGuesser +from .utils import get_event_qs logger = logging.getLogger(__name__) @@ -161,26 +159,6 @@ class PaginatorFilter(Paginator): return page -# -# -# Useful for translation -to_be_translated = [ - _("Recurrent import name"), - _("Add another"), - _("Browse..."), - _("No file selected."), -] - - -def get_event_qs(request): - if request.user.is_authenticated: - return Event.objects.filter() - else: - return Event.objects.filter(status=Event.STATUS.PUBLISHED) - - - - def home(request, cat=None): return week_view(request, home=True, cat=cat) @@ -2622,84 +2600,6 @@ class OrganisationDeleteView(PermissionRequiredMixin, DeleteView): success_url = reverse_lazy("view_organisations") -######################### -## Tags -######################### - - -class TagUpdateView(PermissionRequiredMixin, SuccessMessageMixin, UpdateView): - model = Tag - permission_required = "agenda_culturel.change_tag" - form_class = TagForm - success_message = _("The tag has been successfully updated.") - - -class TagCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView): - model = Tag - permission_required = "agenda_culturel.add_tag" - form_class = TagForm - success_message = _("The tag has been successfully created.") - - def get_initial(self, *args, **kwargs): - initial = super().get_initial(**kwargs) - if "name" in self.request.GET: - initial["name"] = self.request.GET.get("name") - return initial - - def form_valid(self, form): - Tag.clear_cache() - return super().form_valid(form) - - -class TagDeleteView(PermissionRequiredMixin, DeleteView): - model = Tag - permission_required = "agenda_culturel.delete_tag" - success_url = reverse_lazy("view_all_tags") - - -def view_tag_past(request, t): - return view_tag(request, t, True) - - -def view_tag(request, t, past=False): - now = date.today() - - qs = get_event_qs(request).filter(tags__contains=[t]) - - if past: - qs = qs.filter(start_day__lt=now).order_by("-start_day", "-start_time") - else: - qs = qs.filter(start_day__gte=now).order_by("start_day", "start_time") - - qs = qs.filter( - Q(other_versions__isnull=True) - | Q(other_versions__representative=F("pk")) - | Q(other_versions__representative__isnull=True) - ) - - paginator = Paginator(qs, 10) - page = request.GET.get("page") - - try: - response = paginator.page(page) - except PageNotAnInteger: - response = paginator.page(1) - except EmptyPage: - response = paginator.page(paginator.num_pages) - - rimports = RecurrentImport.objects.filter(defaultTags__contains=[t]) - - tag = Tag.objects.filter(name=t).first() - context = { - "tag": t, - "paginator_filter": response, - "object": tag, - "rimports": rimports, - "past": past, - } - return render(request, "agenda_culturel/tag.html", context) - - def statistics(request, pk=None): if pk is not None: rimport = RecurrentImport.objects.filter(pk=pk) @@ -2811,171 +2711,6 @@ def statistics(request, pk=None): return render(request, "agenda_culturel/rimport-statistics.html", context) -def tag_list(request): - tags = Event.get_all_tags() - r_tags = [t["tag"] for t in tags] - objects = Tag.objects.order_by("name").all() - d_objects = dict() - for o in objects: - d_objects[o.name] = o - - tags = [ - t | {"obj": d_objects[t["tag"]]} if t["tag"] in d_objects else t for t in tags - ] - tags += [ - {"obj": o, "tag": o.name, "count": 0} for o in objects if o.name not in r_tags - ] - - context = { - "tags": sorted( - tags, - key=lambda x: emoji.demojize( - remove_accents(x["tag"]).lower(), delimiters=("000", "") - ), - ) - } - return render(request, "agenda_culturel/tags.html", context) - - -@login_required(login_url="/accounts/login/") -@permission_required("agenda_culturel.change_tag") -def rename_tag(request, t): - form = TagRenameForm(name=t) - - if request.method == "POST": - form = TagRenameForm(request.POST, name=t) - if form.is_valid(): - save = True - if form.cleaned_data["name"] == t: - messages.warning( - request, - _("You have not modified the tag name."), - ) - save = False - elif not form.is_force(): - if ( - Event.objects.filter( - tags__contains=[form.cleaned_data["name"]] - ).count() - > 0 - ): - if Tag.objects.filter(name=form.cleaned_data["name"]): - messages.warning( - request, - ( - _( - "This tag {} is already in use, and is described by different information from the current tag. You can force renaming by checking the corresponding option. The information associated with tag {} will be deleted, and all events associated with tag {} will be associated with tag {}." - ) - ).format( - form.cleaned_data["name"], - t, - t, - form.cleaned_data["name"], - ), - ) - else: - messages.warning( - request, - ( - _( - "This tag {} is already in use. You can force renaming by checking the corresponding option." - ) - ).format(form.cleaned_data["name"]), - ) - save = False - form = TagRenameForm(request.POST, name=t, force=True) - - if save: - # find all matching events and update them - events = Event.objects.filter(tags__contains=[t]) - new_name = form.cleaned_data["name"] - for e in events: - e.tags = [te for te in e.tags if te != t] - if new_name not in e.tags: - e.tags += [new_name] - Event.objects.bulk_update(events, fields=["tags"]) - - # find all recurrent imports and fix them - rimports = RecurrentImport.objects.filter(defaultTags__contains=[t]) - for ri in rimports: - ri.tags = [te for te in ri.defaultTags if te != t] - if new_name not in ri.tags: - ri.tags += [new_name] - RecurrentImport.objects.bulk_update(rimports, fields=["defaultTags"]) - - # find tag object - tag_object = Tag.objects.filter(name=t).first() - if tag_object: - tag_object.name = new_name - tag_object.save() - - messages.success( - request, - (_("The tag {} has been successfully renamed to {}.")).format( - t, form.cleaned_data["name"] - ), - ) - return HttpResponseRedirect( - reverse_lazy("view_tag", kwargs={"t": form.cleaned_data["name"]}) - ) - - nb = Event.objects.filter(tags__contains=[t]).count() - - return render( - request, - "agenda_culturel/tag_rename_form.html", - context={"form": form, "tag": t, "nb": nb}, - ) - - -@login_required(login_url="/accounts/login/") -@permission_required("agenda_culturel.delete_tag") -def delete_tag(request, t): - respage = reverse_lazy("view_all_tags") - - if request.method == "POST": - # remove tag from events - events = Event.objects.filter(tags__contains=[t]) - for e in events: - e.tags = [te for te in e.tags if te != t] - Event.objects.bulk_update(events, fields=["tags"]) - - # remove tag from recurrent imports - rimports = RecurrentImport.objects.filter(defaultTags__contains=[t]) - for ri in rimports: - ri.tags = [te for te in ri.defaultTags if te != t] - RecurrentImport.objects.bulk_update(rimports, fields=["defaultTags"]) - - # find tag object - tag_object = Tag.objects.filter(name=t).first() - if tag_object: - tag_object.delete() - - messages.success( - request, - (_("The tag {} has been successfully deleted.")).format(t), - ) - return HttpResponseRedirect(respage) - else: - nb = Event.objects.filter(tags__contains=[t]).count() - obj = Tag.objects.filter(name=t).first() - nbi = RecurrentImport.objects.filter(defaultTags__contains=[t]).count() - cancel_url = request.META.get("HTTP_REFERER", "") - if cancel_url == "": - cancel_url = respage - return render( - request, - "agenda_culturel/tag_confirm_delete_by_name.html", - { - "tag": t, - "nb": nb, - "nbi": nbi, - "cancel_url": cancel_url, - "obj": obj, - }, - ) - - def clear_cache(request): if request.method == "POST": cache.clear() diff --git a/src/agenda_culturel/views/tag_views.py b/src/agenda_culturel/views/tag_views.py new file mode 100644 index 0000000..6734b61 --- /dev/null +++ b/src/agenda_culturel/views/tag_views.py @@ -0,0 +1,270 @@ +from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.mixins import ( + PermissionRequiredMixin, +) +from django.contrib.messages.views import SuccessMessageMixin +from django.views.generic.edit import ( + CreateView, + DeleteView, + UpdateView, +) +from ..forms import ( + TagForm, + TagRenameForm, +) +from ..models import ( + Tag, + RecurrentImport, + Event, + remove_accents, +) +from django.utils.translation import gettext_lazy as _ +from django.urls import reverse_lazy +from datetime import date +from .utils import get_event_qs +from django.db.models import F, Q +from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator +from django.shortcuts import render +from django.contrib import messages +from django.http import ( + HttpResponseRedirect, +) +import emoji + + +class TagUpdateView(PermissionRequiredMixin, SuccessMessageMixin, UpdateView): + model = Tag + permission_required = "agenda_culturel.change_tag" + form_class = TagForm + success_message = _("The tag has been successfully updated.") + + +class TagCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView): + model = Tag + permission_required = "agenda_culturel.add_tag" + form_class = TagForm + success_message = _("The tag has been successfully created.") + + def get_initial(self, *args, **kwargs): + initial = super().get_initial(**kwargs) + if "name" in self.request.GET: + initial["name"] = self.request.GET.get("name") + return initial + + def form_valid(self, form): + Tag.clear_cache() + return super().form_valid(form) + + +class TagDeleteView(PermissionRequiredMixin, DeleteView): + model = Tag + permission_required = "agenda_culturel.delete_tag" + success_url = reverse_lazy("view_all_tags") + + +def view_tag_past(request, t): + return view_tag(request, t, True) + + +def view_tag(request, t, past=False): + now = date.today() + + qs = get_event_qs(request).filter(tags__contains=[t]) + + if past: + qs = qs.filter(start_day__lt=now).order_by("-start_day", "-start_time") + else: + qs = qs.filter(start_day__gte=now).order_by("start_day", "start_time") + + qs = qs.filter( + Q(other_versions__isnull=True) + | Q(other_versions__representative=F("pk")) + | Q(other_versions__representative__isnull=True) + ) + + paginator = Paginator(qs, 10) + page = request.GET.get("page") + + try: + response = paginator.page(page) + except PageNotAnInteger: + response = paginator.page(1) + except EmptyPage: + response = paginator.page(paginator.num_pages) + + rimports = RecurrentImport.objects.filter(defaultTags__contains=[t]) + + tag = Tag.objects.filter(name=t).first() + context = { + "tag": t, + "paginator_filter": response, + "object": tag, + "rimports": rimports, + "past": past, + } + return render(request, "agenda_culturel/tag.html", context) + + +def tag_list(request): + tags = Event.get_all_tags() + r_tags = [t["tag"] for t in tags] + objects = Tag.objects.order_by("name").all() + d_objects = dict() + for o in objects: + d_objects[o.name] = o + + tags = [ + t | {"obj": d_objects[t["tag"]]} if t["tag"] in d_objects else t for t in tags + ] + tags += [ + {"obj": o, "tag": o.name, "count": 0} for o in objects if o.name not in r_tags + ] + + context = { + "tags": sorted( + tags, + key=lambda x: emoji.demojize( + remove_accents(x["tag"]).lower(), delimiters=("000", "") + ), + ) + } + return render(request, "agenda_culturel/tags.html", context) + + +@login_required(login_url="/accounts/login/") +@permission_required("agenda_culturel.change_tag") +def rename_tag(request, t): + form = TagRenameForm(name=t) + + if request.method == "POST": + form = TagRenameForm(request.POST, name=t) + if form.is_valid(): + save = True + if form.cleaned_data["name"] == t: + messages.warning( + request, + _("You have not modified the tag name."), + ) + save = False + elif not form.is_force(): + if ( + Event.objects.filter( + tags__contains=[form.cleaned_data["name"]] + ).count() + > 0 + ): + if Tag.objects.filter(name=form.cleaned_data["name"]): + messages.warning( + request, + ( + _( + "This tag {} is already in use, and is described by different information from the current tag. You can force renaming by checking the corresponding option. The information associated with tag {} will be deleted, and all events associated with tag {} will be associated with tag {}." + ) + ).format( + form.cleaned_data["name"], + t, + t, + form.cleaned_data["name"], + ), + ) + else: + messages.warning( + request, + ( + _( + "This tag {} is already in use. You can force renaming by checking the corresponding option." + ) + ).format(form.cleaned_data["name"]), + ) + save = False + form = TagRenameForm(request.POST, name=t, force=True) + + if save: + # find all matching events and update them + events = Event.objects.filter(tags__contains=[t]) + new_name = form.cleaned_data["name"] + for e in events: + e.tags = [te for te in e.tags if te != t] + if new_name not in e.tags: + e.tags += [new_name] + Event.objects.bulk_update(events, fields=["tags"]) + + # find all recurrent imports and fix them + rimports = RecurrentImport.objects.filter(defaultTags__contains=[t]) + for ri in rimports: + ri.tags = [te for te in ri.defaultTags if te != t] + if new_name not in ri.tags: + ri.tags += [new_name] + RecurrentImport.objects.bulk_update(rimports, fields=["defaultTags"]) + + # find tag object + tag_object = Tag.objects.filter(name=t).first() + if tag_object: + tag_object.name = new_name + tag_object.save() + + messages.success( + request, + (_("The tag {} has been successfully renamed to {}.")).format( + t, form.cleaned_data["name"] + ), + ) + return HttpResponseRedirect( + reverse_lazy("view_tag", kwargs={"t": form.cleaned_data["name"]}) + ) + + nb = Event.objects.filter(tags__contains=[t]).count() + + return render( + request, + "agenda_culturel/tag_rename_form.html", + context={"form": form, "tag": t, "nb": nb}, + ) + + +@login_required(login_url="/accounts/login/") +@permission_required("agenda_culturel.delete_tag") +def delete_tag(request, t): + respage = reverse_lazy("view_all_tags") + + if request.method == "POST": + # remove tag from events + events = Event.objects.filter(tags__contains=[t]) + for e in events: + e.tags = [te for te in e.tags if te != t] + Event.objects.bulk_update(events, fields=["tags"]) + + # remove tag from recurrent imports + rimports = RecurrentImport.objects.filter(defaultTags__contains=[t]) + for ri in rimports: + ri.tags = [te for te in ri.defaultTags if te != t] + RecurrentImport.objects.bulk_update(rimports, fields=["defaultTags"]) + + # find tag object + tag_object = Tag.objects.filter(name=t).first() + if tag_object: + tag_object.delete() + + messages.success( + request, + (_("The tag {} has been successfully deleted.")).format(t), + ) + return HttpResponseRedirect(respage) + else: + nb = Event.objects.filter(tags__contains=[t]).count() + obj = Tag.objects.filter(name=t).first() + nbi = RecurrentImport.objects.filter(defaultTags__contains=[t]).count() + cancel_url = request.META.get("HTTP_REFERER", "") + if cancel_url == "": + cancel_url = respage + return render( + request, + "agenda_culturel/tag_confirm_delete_by_name.html", + { + "tag": t, + "nb": nb, + "nbi": nbi, + "cancel_url": cancel_url, + "obj": obj, + }, + ) diff --git a/src/agenda_culturel/views/utils.py b/src/agenda_culturel/views/utils.py new file mode 100644 index 0000000..a9f8b39 --- /dev/null +++ b/src/agenda_culturel/views/utils.py @@ -0,0 +1,19 @@ +from ..models import Event +from django.utils.translation import gettext_lazy as _ + +# +# +# Useful for translation +to_be_translated = [ + _("Recurrent import name"), + _("Add another"), + _("Browse..."), + _("No file selected."), +] + + +def get_event_qs(request): + if request.user.is_authenticated: + return Event.objects.filter() + else: + return Event.objects.filter(status=Event.STATUS.PUBLISHED)