Use JQuery for dynamic lookups of form fields instead of using a large <select> list

This commit is contained in:
Michael Hall 2018-01-20 14:09:57 -05:00
parent 816e770ccf
commit c2195f22ae
11 changed files with 219 additions and 7 deletions

View file

@ -1,7 +1,47 @@
from django.forms import ModelForm from django.utils.safestring import mark_safe
from django.forms import ModelForm, Field
from django.forms.widgets import Select, Media
from .models.profiles import Team from .models.profiles import Team
from .models.events import Event from .models.events import Event
class LookupMedia(Media):
def render(self):
return mark_safe('''<script type="text/javascript"><script>
$(document).ready(function(){
$("#{{ widget.name }}_search").keyup(function() {
var searchText = this.value;
$.getJSON("{{ widget.source }}?q="+searchText, function(data) {
var selectField = $("#{{ widget.name }}_select");
selectField.empty();
$.each(data, function(){
selectField.append('<option value="'+ this.{{ widget.key }} +'">'+ this.{{ widget.label }} + '</option>')
});
});
});
});
</script>''')
class Lookup(Select):
input_type = 'select'
template_name = 'forms/widgets/lookup.html'
add_id_index = False
checked_attribute = {'selected': True}
option_inherits_attrs = False
def __init__(self, source='#', key="id", label="name", attrs=None):
super().__init__(attrs)
self.source = source
self.key = key
self.label = label
self.name = 'place'
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context['widget']['source'] = self.source
context['widget']['key'] = self.key
context['widget']['label'] = self.label
return context
class TeamForm(ModelForm): class TeamForm(ModelForm):
class Meta: class Meta:
model = Team model = Team
@ -11,14 +51,28 @@ class NewTeamForm(ModelForm):
class Meta: class Meta:
model = Team model = Team
fields = ['name', 'country', 'spr', 'city', 'web_url', 'tz'] fields = ['name', 'country', 'spr', 'city', 'web_url', 'tz']
widgets = {
'country': Lookup(source='/api/country/', label='name'),
'spr': Lookup(source='/api/spr/', label='name'),
'city': Lookup(source='/api/cities/', label='name'),
}
class TeamEventForm(ModelForm): class TeamEventForm(ModelForm):
class Meta: class Meta:
model = Event model = Event
fields = ['name', 'start_time', 'end_time', 'summary', 'place', 'web_url', 'announce_url', 'tags'] fields = ['name', 'start_time', 'end_time', 'summary', 'place', 'web_url', 'announce_url', 'tags']
widgets = {
'country': Lookup(source='/api/country/', label='name'),
'spr': Lookup(source='/api/spr/', label='name'),
'city': Lookup(source='/api/cities/', label='name'),
}
class NewTeamEventForm(ModelForm): class NewTeamEventForm(ModelForm):
class Meta: class Meta:
model = Event model = Event
fields = ['name', 'start_time', 'end_time', 'summary', 'place'] fields = ['name', 'start_time', 'end_time', 'summary', 'place']
widgets = {
'place': Lookup(source='/api/places/', label='name'),
}

View file

@ -4,6 +4,8 @@ from django.contrib.auth.models import User, Group
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import reverse from django.shortcuts import reverse
from rest_framework import serializers
from .locale import * from .locale import *
from .profiles import * from .profiles import *
from .search import * from .search import *

View file

@ -1,6 +1,8 @@
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
import pytz import pytz
class Language(models.Model): class Language(models.Model):
@ -41,6 +43,15 @@ class Country(models.Model):
else: else:
return 'no_country' return 'no_country'
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = (
'id',
'name',
'code'
)
class SPR(models.Model): class SPR(models.Model):
name = models.CharField(_("Name"), max_length=100) name = models.CharField(_("Name"), max_length=100)
code = models.CharField(_("Admin Code"), max_length=8) code = models.CharField(_("Admin Code"), max_length=8)
@ -59,6 +70,17 @@ class SPR(models.Model):
else: else:
return 'no_spr' return 'no_spr'
class SPRSerializer(serializers.ModelSerializer):
class Meta:
model = SPR
fields = (
'id',
'name',
'code',
'country',
'slug'
)
class City(models.Model): class City(models.Model):
class Meta: class Meta:
ordering = ('name',) ordering = ('name',)
@ -79,4 +101,14 @@ class City(models.Model):
return 'no_city' return 'no_city'
class CitySerializer(serializers.ModelSerializer):
class Meta:
model = City
fields = (
'id',
'name',
'spr',
'tz',
'slug'
)

View file

@ -0,0 +1,5 @@
<select autocomplete="false" id="{{ widget.name }}_select" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %} style="width: 300px">
<option value="">--------</option>
</select>
<input autocomplete="false" id="{{ widget.name }}_search" name="{{ widget.name }}_search" />

View file

@ -1,8 +1,12 @@
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from rest_framework.decorators import api_view, throttle_classes
from rest_framework.response import Response
from .models.search import Searchable, SearchableSerializer from .models.search import Searchable, SearchableSerializer
from .models.events import Event from .models.events import Event, Place, PlaceSerializer
from .models.locale import Country ,CountrySerializer, SPR, SPRSerializer, City, CitySerializer
import simplejson import simplejson
@ -18,3 +22,52 @@ def events_list(request, *args, **kwargs):
'events_list': events, 'events_list': events,
} }
return render(request, 'events/event_list.html', context) return render(request, 'events/event_list.html', context)
@api_view(['GET'])
def places_list(request, *args, **kwargs):
places = Place.objects.all()
if "q" in request.GET:
match = request.GET.get("q", "")
places = Place.objects.filter(name__icontains=match)
else:
places = Place.objects.all()
serializer = PlaceSerializer(places, many=True)
return Response(serializer.data)
@api_view(['GET'])
def country_list(request, *args, **kwargs):
if "q" in request.GET:
match = request.GET.get("q", "")
countries = Country.objects.filter(name__icontains=match)
else:
countries = Country.objects.all()
serializer = CountrySerializer(countries, many=True)
return Response(serializer.data)
@api_view(['GET'])
def spr_list(request, *args, **kwargs):
if "q" in request.GET:
match = request.GET.get("q", "")
sprs = SPR.objects.filter(name__icontains=match)
else:
sprs = SPR.objects.all()
if "country" in request.GET and request.GET.get("country") is not "":
sprs = sprs.filter(country=request.GET.get("country"))
serializer = SPRSerializer(sprs, many=True)
return Response(serializer.data)
@api_view(['GET'])
def city_list(request, *args, **kwargs):
if "q" in request.GET:
match = request.GET.get("q", "")
cities = City.objects.filter(name__icontains=match)
else:
cities = City.objects.all()
if "spr" in request.GET and request.GET.get("spr") is not "":
cities = cities.filter(spr=request.GET.get("spr"))
serializer = CitySerializer(cities, many=True)
return Response(serializer.data)

View file

@ -149,3 +149,4 @@ USE_TZ = True
# https://docs.djangoproject.com/en/2.0/howto/static-files/ # https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'

View file

@ -10,7 +10,7 @@
<!-- Bootstrap core CSS --> <!-- Bootstrap core CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy" crossorigin="anonymous" rel="stylesheet"> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy" crossorigin="anonymous" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.css" rel="stylesheet">
<!-- Bootstrap style overrides --> <!-- Bootstrap style overrides -->
<style> <style>
body { body {
@ -60,8 +60,10 @@ body {
<!-- Bootstrap core JavaScript <!-- Bootstrap core JavaScript
================================================== --> ================================================== -->
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery-slim.min.js"><\/script>')</script> <script>window.jQuery || document.write('<script src="../../../../assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
<script src="../../../../assets/js/vendor/popper.min.js"></script> <script src="https://unpkg.com/popper.js@1.12.9/dist/umd/popper.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script> </body> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.js"></script>
{% block javascript %}{% endblock %}
</html> </html>

View file

@ -12,3 +12,21 @@
</form> </form>
{% endblock %} {% endblock %}
{% block javascript %}
{% with event_form.fields.place.widget as widget %}
<script type="text/javascript">
$(document).ready(function(){
$("#{{ widget.name }}_search").keyup(function() {
var searchText = this.value;
$.getJSON("{{ widget.source }}?q="+searchText, function(data) {
var selectField = $("#{{ widget.name }}_select");
selectField.empty();
$.each(data, function(){
selectField.append('<option value="'+ this.{{ widget.key }} +'">'+ this.name+' '+this.city + '</option>')
});
});
});
});
</script>
{% endwith %}
{% endblock %}

View file

@ -10,3 +10,42 @@
</form> </form>
{% endblock %} {% endblock %}
{% block javascript %}
<script type="text/javascript">
$(document).ready(function(){
$("#country_search").keyup(function() {
var searchText = this.value;
$.getJSON("/api/countries/?q="+searchText, function(data) {
var selectField = $("#country_select");
selectField.empty();
$.each(data, function(){
selectField.append('<option value="'+ this.id +'">'+ this.name + '</option>')
});
});
});
$("#spr_search").keyup(function() {
var searchText = this.value;
var country_id = $("#country_select")[0].selectedOptions[0].value;
$.getJSON("/api/spr/?q="+searchText+"&country="+country_id, function(data) {
var selectField = $("#spr_select");
selectField.empty();
$.each(data, function(){
selectField.append('<option value="'+ this.id +'">'+ this.name + '</option>')
});
});
});
$("#city_search").keyup(function() {
var searchText = this.value;
var spr_id = $("#spr_select")[0].selectedOptions[0].value;
$.getJSON("/api/cities/?q="+searchText+"&spr="+spr_id, function(data) {
var selectField = $("#city_select");
selectField.empty();
$.each(data, function(){
selectField.append('<option value="'+ this.id +'">'+ this.name + '</option>')
});
});
});
});
</script>
{% endblock %}

View file

@ -4,10 +4,12 @@
<center> <center>
<h2>Welcome to Get Together!</h2> <h2>Welcome to Get Together!</h2>
<h3>Login</h3> <h4>Login</h4>
<!--
<a class="btn btn-primary" href="{% url 'social:begin' 'google-oauth2' %}">Google</a>&nbsp; &nbsp; <a class="btn btn-primary" href="{% url 'social:begin' 'google-oauth2' %}">Google</a>&nbsp; &nbsp;
<a class="btn btn-primary" href="{% url 'social:begin' 'facebook' %}">Facebook</a><br/><br/> <a class="btn btn-primary" href="{% url 'social:begin' 'facebook' %}">Facebook</a><br/><br/>
<a class="btn btn-primary" href="{% url 'social:begin' 'twitter' %}">Twitter</a>&nbsp; &nbsp; <a class="btn btn-primary" href="{% url 'social:begin' 'twitter' %}">Twitter</a>&nbsp; &nbsp;
-->
<a class="btn btn-primary" href="{% url 'social:begin' 'github' %}">GitHub</a><br/> <a class="btn btn-primary" href="{% url 'social:begin' 'github' %}">GitHub</a><br/>
<br> <br>
</center> </center>

View file

@ -22,6 +22,10 @@ urlpatterns = [
path('', views.home, name='home'), path('', views.home, name='home'),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('searchables/', event_views.searchable_list), path('searchables/', event_views.searchable_list),
path('api/places/', event_views.places_list),
path('api/countries/', event_views.country_list),
path('api/spr/', event_views.spr_list),
path('api/cities/', event_views.city_list),
path('events/', views.events_list, name='events'), path('events/', views.events_list, name='events'),
path('create-team/', views.create_team, name='create-team'), path('create-team/', views.create_team, name='create-team'),