Ajout de suggestions dans les filtres d'inclusion et exclusion

This commit is contained in:
Jean-Marie Favreau 2024-11-13 23:52:02 +01:00
parent ed2f530f0c
commit fe1061e638
7 changed files with 424 additions and 351 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.9 on 2024-11-13 17:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0109_delete_moderationanswer_delete_moderationquestion'),
]
operations = [
migrations.AddField(
model_name='tag',
name='in_excluded_suggestions',
field=models.BooleanField(default=False, help_text='This tag will be part of the excluded suggestions.', verbose_name='In excluded suggestions'),
),
migrations.AddField(
model_name='tag',
name='in_included_suggestions',
field=models.BooleanField(default=False, help_text='This tag will be part of the included suggestions.', verbose_name='In included suggestions'),
),
]

View File

@ -1,4 +1,5 @@
from django.db import models, connection
from django.core.exceptions import FieldDoesNotExist
from django_better_admin_arrayfield.models.fields import ArrayField
from django.utils.translation import gettext_lazy as _
from django.utils.safestring import mark_safe
@ -186,6 +187,18 @@ class Tag(models.Model):
default=True,
)
in_excluded_suggestions = models.BooleanField(
verbose_name=_("In excluded suggestions"),
help_text=_("This tag will be part of the excluded suggestions."),
default=False,
)
in_included_suggestions = models.BooleanField(
verbose_name=_("In included suggestions"),
help_text=_("This tag will be part of the included suggestions."),
default=False,
)
category = models.ForeignKey(
Category,
verbose_name=_("Category"),
@ -199,6 +212,42 @@ class Tag(models.Model):
def get_absolute_url(self):
return reverse("view_tag", kwargs={"t": self.name})
def get_tag_groups(nb_suggestions=10, exclude=False, include=False):
free_tags = Event.get_all_tags(False)
obj_tags = Tag.objects
try:
if exclude:
obj_tags = obj_tags.filter(Q(in_excluded_suggestions=True))
if include:
obj_tags = obj_tags.filter(Q(in_included_suggestions=True)|Q(principal=True))
except FieldDoesNotExist:
pass
if not exclude and not include:
obj_tags = obj_tags.filter(principal=True)
obj_tags = obj_tags.values_list("name", flat=True)
if len(obj_tags) > nb_suggestions:
nb_suggestions = len(obj_tags)
tags = [{"tag": t["tag"], "count": 1000000 if t["tag"] in obj_tags else t["count"]} for t in free_tags]
tags.sort(key=lambda x: -x["count"])
tags1 = tags[0:nb_suggestions]
tags1.sort(key=lambda x: remove_accents(x["tag"]).lower())
tags2 = tags[nb_suggestions:]
tags2.sort(key=lambda x: remove_accents(x["tag"]).lower())
result = ((_('Suggestions'), [(t["tag"], t["tag"]) for t in tags1]),
(_('Others'), [(t["tag"], t["tag"]) for t in tags2]))
return result
class DuplicatedEvents(models.Model):
@ -626,14 +675,17 @@ class Event(models.Model):
verbose_name_plural = _("Events")
permissions = [("set_duplicated_event", "Can set an event as duplicated")]
def get_all_tags():
def get_all_tags(sort=True):
cursor = connection.cursor()
raw_query = """
select unnest(subquery_alias.tags) as distinct_tags, count(*) as tags_group_by_count
from (select tags from agenda_culturel_event) as subquery_alias group by distinct_tags;
"""
cursor.execute(raw_query)
return [{"tag": row[0], "count": row[1]} for row in cursor]
result = [{"tag": row[0], "count": row[1]} for row in cursor]
if sort:
result.sort(key=lambda x: remove_accents(x["tag"].lower()))
return result
def is_draft(self):
return self.status == Event.STATUS.DRAFT

View File

@ -1252,15 +1252,31 @@ img.preview {
}
}
.choices__list--dropdown {
display: none;
font-size: 80%;
display: block;
will-change: display;
background: var(--background-color);
padding: 0.4em;
border-radius: 0.2em;
box-shadow: 0 -1px 0 rgba(115, 130, 140, 0.2);
position: relative;
max-height: 300px;
overflow: auto;
-webkit-overflow-scrolling: touch;
will-change: scroll-position;
}
.choices__list--dropdown.is-active {
.choices__list--dropdown .choices__group,
.choices__list--dropdown .choices__item {
display: none;
}
.choices__list--dropdown .choices__item.visible,
.choices__list--dropdown.is-active .choices__item {
display: inline-block;
}
.choices__list--dropdown .choices__group.visible,
.choices__list--dropdown.is-active .choices__group {
display: block;
}
}

View File

@ -20,7 +20,7 @@
{{ t | tag_button:"true" }}
{% endfor %}
{% for t in filter.get_exclude_tags %}
{{ t | tag_button_strike }}
{{ t | tag_button_strike:"true" }}
{% endfor %}
{% for s in filter.get_status_names %}
{{ s }}
@ -74,6 +74,23 @@
</div>
<script src="{% static 'choicejs/choices.min.js' %}"></script>
<script>
show_firstgroup = {
choice(classes, choice) {
const i = Choices.defaults.templates.choice.call(this, classes, choice);
if (this.first_group !== null && choice.groupId == this.first_group)
i.classList.add("visible");
return i;
},
choiceGroup(classes, group) {
const g = Choices.defaults.templates.choiceGroup.call(this, classes, group);
if (this.first_group === undefined && group.value == "Suggestions")
this.first_group = group.id;
if (this.first_group !== null && group.id == this.first_group)
g.classList.add("visible");
return g;
}
};
const position = document.querySelector('#id_position');
const choices_position = new Choices(position);
const tags = document.querySelector('#id_tags');
@ -84,7 +101,9 @@
delimiter: ',',
editItems: true,
removeItemButton: true,
}
shouldSort: false,
callbackOnCreateTemplates: () => (show_firstgroup)
}
);
const exclude_tags = document.querySelector('#id_exclude_tags');
const choices_exclude_tags = new Choices(exclude_tags,
@ -94,13 +113,11 @@
delimiter: ',',
editItems: true,
removeItemButton: true,
shouldSort: false,
callbackOnCreateTemplates: () => (show_firstgroup)
}
);
document.querySelector("#id_position .choices__inner").classList.add("contrast");
document.querySelector("#id_position .choices__inner").classList.add("outline");
document.querySelector("#id_position .choices__inner").setAttribute("role", "button");
</script>
{% if noarticle == 0 %}

View File

@ -26,11 +26,23 @@
{% endif %}
{% else %}
{% if perms.agenda_culturel.add_tag %}
<a href="{% url 'add_tag' %}?name={{ tag }}" role="button">Renseigner l'étiquette</a>
<a href="{% url 'add_tag' %}?name={{ tag|urlencode }}" role="button">Renseigner l'étiquette</a>
{% endif %}
{% endif %}
</div>
<h1>Les événements <em>{{ tag }}</em></h1></header>
<h1>Les événements <em>{{ tag }}</em></h1>
{% if user.is_authenticated and object %}
{% if object.in_excluded_suggestions %}
<p class="remarque">Cette étiquette fait partie des étiquettes suggérées pour l'exclusion.</p>
{% endif %}
{% if object.in_included_suggestions %}
<p class="remarque">Cette étiquette fait partie des étiquettes suggérées pour l'inclusion.</p>
{% endif %}
{% if object.principal %}
<p class="remarque">Cette étiquette fait partie des étiquettes principales mises en avant.</p>
{% endif %}
{% endif %}
</header>
{% if object %}

View File

@ -164,7 +164,7 @@ class EventFilter(django_filters.FilterSet):
position = django_filters.ModelChoiceFilter(
label="À proximité de",
method="no_filter",
empty_label=_("Choisir une localité"),
empty_label=_("Select a location"),
queryset=ReferenceLocation.objects.all().order_by("-main", "name__unaccent")
)
@ -178,7 +178,7 @@ class EventFilter(django_filters.FilterSet):
exclude_tags = django_filters.MultipleChoiceFilter(
label="Exclure les étiquettes",
choices=[(t["tag"], t["tag"]) for t in Event.get_all_tags()],
choices=[],
lookup_expr="icontains",
field_name="tags",
exclude=True,
@ -187,7 +187,7 @@ class EventFilter(django_filters.FilterSet):
tags = django_filters.MultipleChoiceFilter(
label="Inclure les étiquettes",
choices=[(t["tag"], t["tag"]) for t in Event.get_all_tags()],
choices=[],
lookup_expr="icontains",
conjoined=True,
field_name="tags",
@ -224,6 +224,8 @@ class EventFilter(django_filters.FilterSet):
super().__init__(*args, **kwargs)
if not kwargs["request"].user.is_authenticated:
self.form.fields.pop("status")
self.form.fields["exclude_tags"].choices = Tag.get_tag_groups(exclude=True, nb_suggestions=0)
self.form.fields["tags"].choices = Tag.get_tag_groups(include=True)
def filter_recurrences(self, queryset, name, value):
# construct the full lookup expression
@ -396,7 +398,7 @@ class EventFilter(django_filters.FilterSet):
return result
def tag_exists(self, tag):
return tag in [t[0] for t in self.form.fields["tags"].choices]
return tag in [t[0] for g in self.form.fields["tags"].choices for t in g[1]]
def set_default_values(request):
if request.user.is_authenticated:
@ -2145,14 +2147,14 @@ class PlaceFromEventCreateView(PlaceCreateView):
class TagUpdateView(PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
model = Tag
permission_required = "agenda_culturel.change_tag"
fields = ["name", "description", "principal", "category"]
fields = ["name", "description", "principal", "category", "in_excluded_suggestions", "in_included_suggestions"]
success_message = _("The tag has been successfully updated.")
class TagCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
model = Tag
permission_required = "agenda_culturel.add_tag"
fields = ["name", "description", "principal", "category"]
fields = ["name", "description", "principal", "category", "in_excluded_suggestions", "in_included_suggestions"]
success_message = _("The tag has been successfully created.")
def get_initial(self, *args, **kwargs):