Ajout de suggestions dans les filtres d'inclusion et exclusion
This commit is contained in:
parent
ed2f530f0c
commit
fe1061e638
File diff suppressed because it is too large
Load Diff
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -1,4 +1,5 @@
|
|||||||
from django.db import models, connection
|
from django.db import models, connection
|
||||||
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django_better_admin_arrayfield.models.fields import ArrayField
|
from django_better_admin_arrayfield.models.fields import ArrayField
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
@ -186,6 +187,18 @@ class Tag(models.Model):
|
|||||||
default=True,
|
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 = models.ForeignKey(
|
||||||
Category,
|
Category,
|
||||||
verbose_name=_("Category"),
|
verbose_name=_("Category"),
|
||||||
@ -199,6 +212,42 @@ class Tag(models.Model):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("view_tag", kwargs={"t": self.name})
|
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):
|
class DuplicatedEvents(models.Model):
|
||||||
|
|
||||||
@ -626,14 +675,17 @@ class Event(models.Model):
|
|||||||
verbose_name_plural = _("Events")
|
verbose_name_plural = _("Events")
|
||||||
permissions = [("set_duplicated_event", "Can set an event as duplicated")]
|
permissions = [("set_duplicated_event", "Can set an event as duplicated")]
|
||||||
|
|
||||||
def get_all_tags():
|
def get_all_tags(sort=True):
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
raw_query = """
|
raw_query = """
|
||||||
select unnest(subquery_alias.tags) as distinct_tags, count(*) as tags_group_by_count
|
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;
|
from (select tags from agenda_culturel_event) as subquery_alias group by distinct_tags;
|
||||||
"""
|
"""
|
||||||
cursor.execute(raw_query)
|
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):
|
def is_draft(self):
|
||||||
return self.status == Event.STATUS.DRAFT
|
return self.status == Event.STATUS.DRAFT
|
||||||
|
@ -1252,15 +1252,31 @@ img.preview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.choices__list--dropdown {
|
.choices__list--dropdown {
|
||||||
display: none;
|
font-size: 80%;
|
||||||
|
display: block;
|
||||||
will-change: display;
|
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;
|
position: relative;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
will-change: scroll-position;
|
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;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
{{ t | tag_button:"true" }}
|
{{ t | tag_button:"true" }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for t in filter.get_exclude_tags %}
|
{% for t in filter.get_exclude_tags %}
|
||||||
{{ t | tag_button_strike }}
|
{{ t | tag_button_strike:"true" }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for s in filter.get_status_names %}
|
{% for s in filter.get_status_names %}
|
||||||
{{ s }}
|
{{ s }}
|
||||||
@ -74,6 +74,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<script src="{% static 'choicejs/choices.min.js' %}"></script>
|
<script src="{% static 'choicejs/choices.min.js' %}"></script>
|
||||||
<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 position = document.querySelector('#id_position');
|
||||||
const choices_position = new Choices(position);
|
const choices_position = new Choices(position);
|
||||||
const tags = document.querySelector('#id_tags');
|
const tags = document.querySelector('#id_tags');
|
||||||
@ -84,7 +101,9 @@
|
|||||||
delimiter: ',',
|
delimiter: ',',
|
||||||
editItems: true,
|
editItems: true,
|
||||||
removeItemButton: true,
|
removeItemButton: true,
|
||||||
}
|
shouldSort: false,
|
||||||
|
callbackOnCreateTemplates: () => (show_firstgroup)
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const exclude_tags = document.querySelector('#id_exclude_tags');
|
const exclude_tags = document.querySelector('#id_exclude_tags');
|
||||||
const choices_exclude_tags = new Choices(exclude_tags,
|
const choices_exclude_tags = new Choices(exclude_tags,
|
||||||
@ -94,13 +113,11 @@
|
|||||||
delimiter: ',',
|
delimiter: ',',
|
||||||
editItems: true,
|
editItems: true,
|
||||||
removeItemButton: 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>
|
</script>
|
||||||
|
|
||||||
{% if noarticle == 0 %}
|
{% if noarticle == 0 %}
|
||||||
|
@ -26,11 +26,23 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if perms.agenda_culturel.add_tag %}
|
{% 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 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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 %}
|
{% if object %}
|
||||||
|
@ -164,7 +164,7 @@ class EventFilter(django_filters.FilterSet):
|
|||||||
position = django_filters.ModelChoiceFilter(
|
position = django_filters.ModelChoiceFilter(
|
||||||
label="À proximité de",
|
label="À proximité de",
|
||||||
method="no_filter",
|
method="no_filter",
|
||||||
empty_label=_("Choisir une localité"),
|
empty_label=_("Select a location"),
|
||||||
queryset=ReferenceLocation.objects.all().order_by("-main", "name__unaccent")
|
queryset=ReferenceLocation.objects.all().order_by("-main", "name__unaccent")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ class EventFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
exclude_tags = django_filters.MultipleChoiceFilter(
|
exclude_tags = django_filters.MultipleChoiceFilter(
|
||||||
label="Exclure les étiquettes",
|
label="Exclure les étiquettes",
|
||||||
choices=[(t["tag"], t["tag"]) for t in Event.get_all_tags()],
|
choices=[],
|
||||||
lookup_expr="icontains",
|
lookup_expr="icontains",
|
||||||
field_name="tags",
|
field_name="tags",
|
||||||
exclude=True,
|
exclude=True,
|
||||||
@ -187,7 +187,7 @@ class EventFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
tags = django_filters.MultipleChoiceFilter(
|
tags = django_filters.MultipleChoiceFilter(
|
||||||
label="Inclure les étiquettes",
|
label="Inclure les étiquettes",
|
||||||
choices=[(t["tag"], t["tag"]) for t in Event.get_all_tags()],
|
choices=[],
|
||||||
lookup_expr="icontains",
|
lookup_expr="icontains",
|
||||||
conjoined=True,
|
conjoined=True,
|
||||||
field_name="tags",
|
field_name="tags",
|
||||||
@ -224,6 +224,8 @@ class EventFilter(django_filters.FilterSet):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if not kwargs["request"].user.is_authenticated:
|
if not kwargs["request"].user.is_authenticated:
|
||||||
self.form.fields.pop("status")
|
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):
|
def filter_recurrences(self, queryset, name, value):
|
||||||
# construct the full lookup expression
|
# construct the full lookup expression
|
||||||
@ -396,7 +398,7 @@ class EventFilter(django_filters.FilterSet):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def tag_exists(self, tag):
|
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):
|
def set_default_values(request):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
@ -2145,14 +2147,14 @@ class PlaceFromEventCreateView(PlaceCreateView):
|
|||||||
class TagUpdateView(PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
|
class TagUpdateView(PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||||
model = Tag
|
model = Tag
|
||||||
permission_required = "agenda_culturel.change_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.")
|
success_message = _("The tag has been successfully updated.")
|
||||||
|
|
||||||
|
|
||||||
class TagCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
|
class TagCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
|
||||||
model = Tag
|
model = Tag
|
||||||
permission_required = "agenda_culturel.add_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.")
|
success_message = _("The tag has been successfully created.")
|
||||||
|
|
||||||
def get_initial(self, *args, **kwargs):
|
def get_initial(self, *args, **kwargs):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user