mirror of
https://gitlab.crans.org/bde/nk20
synced 2024-12-22 23:42:25 +00:00
Custom auto-complete fields, remove DAL requirement
This commit is contained in:
parent
823bcfe781
commit
f09364d3d8
@ -3,7 +3,8 @@
|
||||
|
||||
from django import forms
|
||||
from activity.models import Activity
|
||||
from note_kfet.inputs import DateTimePickerInput
|
||||
from member.models import Club
|
||||
from note_kfet.inputs import DateTimePickerInput, AutocompleteModelSelect
|
||||
|
||||
|
||||
class ActivityForm(forms.ModelForm):
|
||||
@ -11,6 +12,14 @@ class ActivityForm(forms.ModelForm):
|
||||
model = Activity
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
"organizer": AutocompleteModelSelect(
|
||||
model=Club,
|
||||
attrs={"api_url": "/api/members/club/"},
|
||||
),
|
||||
"attendees_club": AutocompleteModelSelect(
|
||||
model=Club,
|
||||
attrs={"api_url": "/api/members/club/"},
|
||||
),
|
||||
"date_start": DateTimePickerInput(),
|
||||
"date_end": DateTimePickerInput(),
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_tables2.views import SingleTableView
|
||||
@ -13,6 +14,7 @@ from .models import Activity
|
||||
class ActivityCreateView(LoginRequiredMixin, CreateView):
|
||||
model = Activity
|
||||
form_class = ActivityForm
|
||||
success_url = reverse_lazy('activity:activity_list')
|
||||
|
||||
|
||||
class ActivityListView(LoginRequiredMixin, SingleTableView):
|
||||
@ -33,6 +35,7 @@ class ActivityDetailView(LoginRequiredMixin, DetailView):
|
||||
class ActivityUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = Activity
|
||||
form_class = ActivityForm
|
||||
success_url = reverse_lazy('activity:activity_list')
|
||||
|
||||
|
||||
class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||
|
@ -4,10 +4,11 @@
|
||||
from crispy_forms.bootstrap import Div
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from note_kfet.inputs import AutocompleteModelSelect
|
||||
from permission.models import PermissionMask
|
||||
|
||||
from .models import Profile, Club, Membership
|
||||
@ -63,11 +64,12 @@ class MembershipForm(forms.ModelForm):
|
||||
# et récupère les noms d'utilisateur valides
|
||||
widgets = {
|
||||
'user':
|
||||
autocomplete.ModelSelect2(
|
||||
url='member:user_autocomplete',
|
||||
AutocompleteModelSelect(
|
||||
User,
|
||||
attrs={
|
||||
'data-placeholder': 'Nom ...',
|
||||
'data-minimum-input-length': 1,
|
||||
'api_url': '/api/user/',
|
||||
'name_field': 'username',
|
||||
'placeholder': 'Nom ...',
|
||||
},
|
||||
),
|
||||
}
|
||||
|
@ -21,6 +21,4 @@ urlpatterns = [
|
||||
path('user/<int:pk>/update_pic', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"),
|
||||
path('user/<int:pk>/aliases', views.ProfileAliasView.as_view(), name="user_alias"),
|
||||
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
|
||||
# API for the user autocompleter
|
||||
path('user/user-autocomplete', views.UserAutocomplete.as_view(), name="user_autocomplete"),
|
||||
]
|
||||
|
@ -4,7 +4,6 @@
|
||||
import io
|
||||
|
||||
from PIL import Image
|
||||
from dal import autocomplete
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
@ -253,28 +252,6 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
||||
return context
|
||||
|
||||
|
||||
class UserAutocomplete(autocomplete.Select2QuerySetView):
|
||||
"""
|
||||
Auto complete users by usernames
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Quand une personne cherche un utilisateur par pseudo, une requête est envoyée sur l'API dédiée à l'auto-complétion.
|
||||
Cette fonction récupère la requête, et renvoie la liste filtrée des utilisateurs par pseudos.
|
||||
"""
|
||||
# Un utilisateur non connecté n'a accès à aucune information
|
||||
if not self.request.user.is_authenticated:
|
||||
return User.objects.none()
|
||||
|
||||
qs = User.objects.filter(PermissionBackend.filter_queryset(self.request.user, User, "view")).all()
|
||||
|
||||
if self.q:
|
||||
qs = qs.filter(username__regex="^" + self.q)
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
# ******************************* #
|
||||
# CLUB #
|
||||
# ******************************* #
|
||||
|
@ -24,7 +24,8 @@ class NotePolymorphicViewSet(ReadOnlyProtectedModelViewSet):
|
||||
"""
|
||||
queryset = Note.objects.all()
|
||||
serializer_class = NotePolymorphicSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ['polymorphic_ctype', 'is_active', ]
|
||||
search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model', ]
|
||||
ordering_fields = ['alias__name', 'alias__normalized_name']
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note_kfet.inputs import AutocompleteModelSelect
|
||||
|
||||
from .models import TransactionTemplate
|
||||
from .models import TransactionTemplate, NoteClub
|
||||
|
||||
|
||||
class ImageForm(forms.Form):
|
||||
@ -30,11 +31,12 @@ class TransactionTemplateForm(forms.ModelForm):
|
||||
# forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special}
|
||||
widgets = {
|
||||
'destination':
|
||||
autocomplete.ModelSelect2(
|
||||
url='note:note_autocomplete',
|
||||
AutocompleteModelSelect(
|
||||
NoteClub,
|
||||
attrs={
|
||||
'data-placeholder': 'Note ...',
|
||||
'data-minimum-input-length': 1,
|
||||
'api_url': '/api/note/note/',
|
||||
'api_url_suffix': '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteClub).pk),
|
||||
'placeholder': 'Note ...',
|
||||
},
|
||||
),
|
||||
}
|
||||
|
@ -13,7 +13,4 @@ urlpatterns = [
|
||||
path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
|
||||
path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
|
||||
path('consos/', views.ConsoView.as_view(), name='consos'),
|
||||
|
||||
# API for the note autocompleter
|
||||
path('note-autocomplete/', views.NoteAutocomplete.as_view(model=Note), name='note_autocomplete'),
|
||||
]
|
||||
|
@ -1,10 +1,8 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from dal import autocomplete
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import CreateView, UpdateView
|
||||
from django_tables2 import SingleTableView
|
||||
@ -13,7 +11,7 @@ from note_kfet.inputs import AmountInput
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
from .forms import TransactionTemplateForm
|
||||
from .models import Transaction, TransactionTemplate, Alias, RecurrentTransaction, NoteSpecial
|
||||
from .models import Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial
|
||||
from .models.transactions import SpecialTransaction
|
||||
from .tables import HistoryTable, ButtonTable
|
||||
|
||||
@ -49,62 +47,6 @@ class TransactionCreateView(LoginRequiredMixin, SingleTableView):
|
||||
return context
|
||||
|
||||
|
||||
class NoteAutocomplete(autocomplete.Select2QuerySetView):
|
||||
"""
|
||||
Auto complete note by aliases. Used in every search field for note
|
||||
ex: :view:`ConsoView`, :view:`TransactionCreateView`
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
When someone look for an :models:`note.Alias`, a query is sent to the dedicated API.
|
||||
This function handles the result and return a filtered list of aliases.
|
||||
"""
|
||||
# Un utilisateur non connecté n'a accès à aucune information
|
||||
if not self.request.user.is_authenticated:
|
||||
return Alias.objects.none()
|
||||
|
||||
qs = Alias.objects.all()
|
||||
|
||||
# self.q est le paramètre de la recherche
|
||||
if self.q:
|
||||
qs = qs.filter(Q(name__regex="^" + self.q) | Q(normalized_name__regex="^" + Alias.normalize(self.q))) \
|
||||
.order_by('normalized_name').distinct()
|
||||
|
||||
# Filtrage par type de note (user, club, special)
|
||||
note_type = self.forwarded.get("note_type", None)
|
||||
if note_type:
|
||||
types = str(note_type).lower()
|
||||
if "user" in types:
|
||||
qs = qs.filter(note__polymorphic_ctype__model="noteuser")
|
||||
elif "club" in types:
|
||||
qs = qs.filter(note__polymorphic_ctype__model="noteclub")
|
||||
elif "special" in types:
|
||||
qs = qs.filter(note__polymorphic_ctype__model="notespecial")
|
||||
else:
|
||||
qs = qs.none()
|
||||
|
||||
return qs
|
||||
|
||||
def get_result_label(self, result):
|
||||
"""
|
||||
Show the selected alias and the username associated
|
||||
<Alias> (aka. <Username> )
|
||||
"""
|
||||
# Gère l'affichage de l'alias dans la recherche
|
||||
res = result.name
|
||||
note_name = str(result.note)
|
||||
if res != note_name:
|
||||
res += " (aka. " + note_name + ")"
|
||||
return res
|
||||
|
||||
def get_result_value(self, result):
|
||||
"""
|
||||
The value used for the transactions will be the id of the Note.
|
||||
"""
|
||||
return str(result.note.pk)
|
||||
|
||||
|
||||
class TransactionTemplateCreateView(LoginRequiredMixin, CreateView):
|
||||
"""
|
||||
Create TransactionTemplate
|
||||
|
@ -1,19 +1,9 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
"""
|
||||
This file comes from the project `django-bootstrap-datepicker-plus` available on Github:
|
||||
https://github.com/monim67/django-bootstrap-datepicker-plus
|
||||
This is distributed under Apache License 2.0.
|
||||
|
||||
This adds datetime pickers with bootstrap.
|
||||
"""
|
||||
|
||||
"""Contains Base Date-Picker input class for widgets of this package."""
|
||||
|
||||
from json import dumps as json_dumps
|
||||
|
||||
from django.forms.widgets import DateTimeBaseInput, NumberInput
|
||||
from django.forms.widgets import DateTimeBaseInput, NumberInput, Select
|
||||
|
||||
|
||||
class AmountInput(NumberInput):
|
||||
@ -30,6 +20,44 @@ class AmountInput(NumberInput):
|
||||
return str(int(100 * float(val))) if val else val
|
||||
|
||||
|
||||
class AutocompleteModelSelect(Select):
|
||||
template_name = "member/autocomplete_model.html"
|
||||
|
||||
def __init__(self, model, attrs=None, choices=()):
|
||||
super().__init__(attrs, choices)
|
||||
|
||||
self.model = model
|
||||
self.model_pk = None
|
||||
|
||||
class Media:
|
||||
"""JS/CSS resources needed to render the date-picker calendar."""
|
||||
|
||||
js = ('js/autocomplete_model.js', )
|
||||
|
||||
def format_value(self, value):
|
||||
if value:
|
||||
self.attrs["model_pk"] = int(value)
|
||||
return str(self.model.objects.get(pk=int(value)))
|
||||
return ""
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
val = super().value_from_datadict(data, files, name)
|
||||
print(data)
|
||||
print(self.attrs)
|
||||
return val
|
||||
|
||||
|
||||
"""
|
||||
The remaining of this file comes from the project `django-bootstrap-datepicker-plus` available on Github:
|
||||
https://github.com/monim67/django-bootstrap-datepicker-plus
|
||||
This is distributed under Apache License 2.0.
|
||||
|
||||
This adds datetime pickers with bootstrap.
|
||||
"""
|
||||
|
||||
"""Contains Base Date-Picker input class for widgets of this package."""
|
||||
|
||||
|
||||
class DatePickerDictionary:
|
||||
"""Keeps track of all date-picker input classes."""
|
||||
|
||||
|
@ -52,9 +52,6 @@ INSTALLED_APPS = [
|
||||
# API
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
# Autocomplete
|
||||
'dal',
|
||||
'dal_select2',
|
||||
|
||||
# Note apps
|
||||
'activity',
|
||||
|
@ -3,7 +3,6 @@ chardet==3.0.4
|
||||
defusedxml==0.6.0
|
||||
Django~=2.2
|
||||
django-allauth==0.39.1
|
||||
django-autocomplete-light==3.5.1
|
||||
django-crispy-forms==1.7.2
|
||||
django-extensions==2.1.9
|
||||
django-filter==2.2.0
|
||||
|
34
static/js/autocomplete_model.js
Normal file
34
static/js/autocomplete_model.js
Normal file
@ -0,0 +1,34 @@
|
||||
$(document).ready(function () {
|
||||
$(".autocomplete").keyup(function(e) {
|
||||
let target = $("#" + e.target.id);
|
||||
let prefix = target.attr("id");
|
||||
let api_url = target.attr("api_url");
|
||||
let api_url_suffix = target.attr("api_url_suffix");
|
||||
if (!api_url_suffix)
|
||||
api_url_suffix = "";
|
||||
let name_field = target.attr("name_field");
|
||||
if (!name_field)
|
||||
name_field = "name";
|
||||
let input = target.val();
|
||||
|
||||
$.getJSON(api_url + "?format=json&search=^" + input + api_url_suffix, function(objects) {
|
||||
let html = "";
|
||||
|
||||
objects.results.forEach(function (obj) {
|
||||
html += li(prefix + "_" + obj.id, obj[name_field]);
|
||||
});
|
||||
|
||||
$("#" + prefix + "_list").html(html);
|
||||
|
||||
objects.results.forEach(function (obj) {
|
||||
$("#" + prefix + "_" + obj.id).click(function() {
|
||||
target.val(obj[name_field]);
|
||||
$("#" + prefix + "_pk").val(obj.id);
|
||||
});
|
||||
|
||||
if (input === obj[name_field])
|
||||
$("#" + prefix + "_pk").val(obj.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
9
templates/member/autocomplete_model.html
Normal file
9
templates/member/autocomplete_model.html
Normal file
@ -0,0 +1,9 @@
|
||||
<input type="hidden" name="{{ widget.name }}" {% if widget.attrs.model_pk %}value="{{ widget.attrs.model_pk }}"{% endif %} id="{{ widget.attrs.id }}_pk">
|
||||
<input class="form-control mx-auto d-block autocomplete" type="text"
|
||||
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
|
||||
name="{{ widget.name }}_name" autocomplete="off"
|
||||
{% for name, value in widget.attrs.items %}
|
||||
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
|
||||
{% endfor %}>
|
||||
<ul class="list-group list-group-flush" id="{{ widget.attrs.id }}_list">
|
||||
</ul>
|
@ -13,8 +13,10 @@
|
||||
<dt class="col-xl-6">{% trans 'name'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ club.name}}</dd>
|
||||
|
||||
<dt class="col-xl-6"><a href="{% url 'member:club_detail' club.parent_club.pk %}">{% trans 'Club Parent'|capfirst %}</a></dt>
|
||||
<dd class="col-xl-6"> {{ club.parent_club.name}}</dd>
|
||||
{% if club.parent_club %}
|
||||
<dt class="col-xl-6"><a href="{% url 'member:club_detail' club.parent_club.pk %}">{% trans 'Club Parent'|capfirst %}</a></dt>
|
||||
<dd class="col-xl-6"> {{ club.parent_club.name}}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-xl-6">{% trans 'membership start'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ club.membership_start }}</dd>
|
||||
|
Loading…
Reference in New Issue
Block a user