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.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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 %}
|
||||
|
@ -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 %}
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user