mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-31 15:50:03 +01:00 
			
		
		
		
	Custom auto-complete fields, remove DAL requirement
This commit is contained in:
		| @@ -3,7 +3,8 @@ | |||||||
|  |  | ||||||
| from django import forms | from django import forms | ||||||
| from activity.models import Activity | 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): | class ActivityForm(forms.ModelForm): | ||||||
| @@ -11,6 +12,14 @@ class ActivityForm(forms.ModelForm): | |||||||
|         model = Activity |         model = Activity | ||||||
|         fields = '__all__' |         fields = '__all__' | ||||||
|         widgets = { |         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_start": DateTimePickerInput(), | ||||||
|             "date_end": DateTimePickerInput(), |             "date_end": DateTimePickerInput(), | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
| from django.views.generic import CreateView, DetailView, UpdateView, TemplateView | from django.views.generic import CreateView, DetailView, UpdateView, TemplateView | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| from django_tables2.views import SingleTableView | from django_tables2.views import SingleTableView | ||||||
| @@ -13,6 +14,7 @@ from .models import Activity | |||||||
| class ActivityCreateView(LoginRequiredMixin, CreateView): | class ActivityCreateView(LoginRequiredMixin, CreateView): | ||||||
|     model = Activity |     model = Activity | ||||||
|     form_class = ActivityForm |     form_class = ActivityForm | ||||||
|  |     success_url = reverse_lazy('activity:activity_list') | ||||||
|  |  | ||||||
|  |  | ||||||
| class ActivityListView(LoginRequiredMixin, SingleTableView): | class ActivityListView(LoginRequiredMixin, SingleTableView): | ||||||
| @@ -33,6 +35,7 @@ class ActivityDetailView(LoginRequiredMixin, DetailView): | |||||||
| class ActivityUpdateView(LoginRequiredMixin, UpdateView): | class ActivityUpdateView(LoginRequiredMixin, UpdateView): | ||||||
|     model = Activity |     model = Activity | ||||||
|     form_class = ActivityForm |     form_class = ActivityForm | ||||||
|  |     success_url = reverse_lazy('activity:activity_list') | ||||||
|  |  | ||||||
|  |  | ||||||
| class ActivityEntryView(LoginRequiredMixin, TemplateView): | class ActivityEntryView(LoginRequiredMixin, TemplateView): | ||||||
|   | |||||||
| @@ -4,10 +4,11 @@ | |||||||
| from crispy_forms.bootstrap import Div | from crispy_forms.bootstrap import Div | ||||||
| from crispy_forms.helper import FormHelper | from crispy_forms.helper import FormHelper | ||||||
| from crispy_forms.layout import Layout | from crispy_forms.layout import Layout | ||||||
| from dal import autocomplete |  | ||||||
| from django import forms | from django import forms | ||||||
| from django.contrib.auth.forms import UserCreationForm, AuthenticationForm | from django.contrib.auth.forms import UserCreationForm, AuthenticationForm | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
|  |  | ||||||
|  | from note_kfet.inputs import AutocompleteModelSelect | ||||||
| from permission.models import PermissionMask | from permission.models import PermissionMask | ||||||
|  |  | ||||||
| from .models import Profile, Club, Membership | from .models import Profile, Club, Membership | ||||||
| @@ -63,11 +64,12 @@ class MembershipForm(forms.ModelForm): | |||||||
|         # et récupère les noms d'utilisateur valides |         # et récupère les noms d'utilisateur valides | ||||||
|         widgets = { |         widgets = { | ||||||
|             'user': |             'user': | ||||||
|                 autocomplete.ModelSelect2( |                 AutocompleteModelSelect( | ||||||
|                     url='member:user_autocomplete', |                     User, | ||||||
|                     attrs={ |                     attrs={ | ||||||
|                         'data-placeholder': 'Nom ...', |                         'api_url': '/api/user/', | ||||||
|                         'data-minimum-input-length': 1, |                         '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>/update_pic', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"), | ||||||
|     path('user/<int:pk>/aliases', views.ProfileAliasView.as_view(), name="user_alias"), |     path('user/<int:pk>/aliases', views.ProfileAliasView.as_view(), name="user_alias"), | ||||||
|     path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), |     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 | import io | ||||||
|  |  | ||||||
| from PIL import Image | from PIL import Image | ||||||
| from dal import autocomplete |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
| @@ -253,28 +252,6 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView): | |||||||
|         return context |         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               # | #              CLUB               # | ||||||
| # ******************************* # | # ******************************* # | ||||||
|   | |||||||
| @@ -24,7 +24,8 @@ class NotePolymorphicViewSet(ReadOnlyProtectedModelViewSet): | |||||||
|     """ |     """ | ||||||
|     queryset = Note.objects.all() |     queryset = Note.objects.all() | ||||||
|     serializer_class = NotePolymorphicSerializer |     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', ] |     search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model', ] | ||||||
|     ordering_fields = ['alias__name', 'alias__normalized_name'] |     ordering_fields = ['alias__name', 'alias__normalized_name'] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,11 +1,12 @@ | |||||||
| # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from dal import autocomplete |  | ||||||
| from django import forms | from django import forms | ||||||
|  | from django.contrib.contenttypes.models import ContentType | ||||||
| from django.utils.translation import gettext_lazy as _ | 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): | 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} |         # forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special} | ||||||
|         widgets = { |         widgets = { | ||||||
|             'destination': |             'destination': | ||||||
|                 autocomplete.ModelSelect2( |                 AutocompleteModelSelect( | ||||||
|                     url='note:note_autocomplete', |                     NoteClub, | ||||||
|                     attrs={ |                     attrs={ | ||||||
|                         'data-placeholder': 'Note ...', |                         'api_url': '/api/note/note/', | ||||||
|                         'data-minimum-input-length': 1, |                         '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/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'), | ||||||
|     path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'), |     path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'), | ||||||
|     path('consos/', views.ConsoView.as_view(), name='consos'), |     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 | # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from dal import autocomplete |  | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
| from django.contrib.contenttypes.models import ContentType | from django.contrib.contenttypes.models import ContentType | ||||||
| from django.db.models import Q |  | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| from django.views.generic import CreateView, UpdateView | from django.views.generic import CreateView, UpdateView | ||||||
| from django_tables2 import SingleTableView | from django_tables2 import SingleTableView | ||||||
| @@ -13,7 +11,7 @@ from note_kfet.inputs import AmountInput | |||||||
| from permission.backends import PermissionBackend | from permission.backends import PermissionBackend | ||||||
|  |  | ||||||
| from .forms import TransactionTemplateForm | 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 .models.transactions import SpecialTransaction | ||||||
| from .tables import HistoryTable, ButtonTable | from .tables import HistoryTable, ButtonTable | ||||||
|  |  | ||||||
| @@ -49,62 +47,6 @@ class TransactionCreateView(LoginRequiredMixin, SingleTableView): | |||||||
|         return context |         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): | class TransactionTemplateCreateView(LoginRequiredMixin, CreateView): | ||||||
|     """ |     """ | ||||||
|     Create TransactionTemplate |     Create TransactionTemplate | ||||||
|   | |||||||
| @@ -1,19 +1,9 @@ | |||||||
| # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay | # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay | ||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # 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 json import dumps as json_dumps | ||||||
|  |  | ||||||
| from django.forms.widgets import DateTimeBaseInput, NumberInput | from django.forms.widgets import DateTimeBaseInput, NumberInput, Select | ||||||
|  |  | ||||||
|  |  | ||||||
| class AmountInput(NumberInput): | class AmountInput(NumberInput): | ||||||
| @@ -30,6 +20,44 @@ class AmountInput(NumberInput): | |||||||
|         return str(int(100 * float(val))) if val else val |         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: | class DatePickerDictionary: | ||||||
|     """Keeps track of all date-picker input classes.""" |     """Keeps track of all date-picker input classes.""" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -52,9 +52,6 @@ INSTALLED_APPS = [ | |||||||
|     # API |     # API | ||||||
|     'rest_framework', |     'rest_framework', | ||||||
|     'rest_framework.authtoken', |     'rest_framework.authtoken', | ||||||
|     # Autocomplete |  | ||||||
|     'dal', |  | ||||||
|     'dal_select2', |  | ||||||
|  |  | ||||||
|     # Note apps |     # Note apps | ||||||
|     'activity', |     'activity', | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ chardet==3.0.4 | |||||||
| defusedxml==0.6.0 | defusedxml==0.6.0 | ||||||
| Django~=2.2 | Django~=2.2 | ||||||
| django-allauth==0.39.1 | django-allauth==0.39.1 | ||||||
| django-autocomplete-light==3.5.1 |  | ||||||
| django-crispy-forms==1.7.2 | django-crispy-forms==1.7.2 | ||||||
| django-extensions==2.1.9 | django-extensions==2.1.9 | ||||||
| django-filter==2.2.0 | 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> |             <dt class="col-xl-6">{% trans 'name'|capfirst %}</dt> | ||||||
|             <dd class="col-xl-6">{{ club.name}}</dd> |             <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> |             {% if club.parent_club %} | ||||||
|             <dd class="col-xl-6"> {{ club.parent_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> | ||||||
|  |             {% endif %} | ||||||
|  |  | ||||||
|             <dt class="col-xl-6">{% trans 'membership start'|capfirst %}</dt> |             <dt class="col-xl-6">{% trans 'membership start'|capfirst %}</dt> | ||||||
|             <dd class="col-xl-6">{{ club.membership_start }}</dd> |             <dd class="col-xl-6">{{ club.membership_start }}</dd> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user