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.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 Meta:
model = Team
@ -11,14 +51,28 @@ class NewTeamForm(ModelForm):
class Meta:
model = Team
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 Meta:
model = Event
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 Meta:
model = Event
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.shortcuts import reverse
from rest_framework import serializers
from .locale import *
from .profiles import *
from .search import *

View file

@ -1,6 +1,8 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
import pytz
class Language(models.Model):
@ -41,6 +43,15 @@ class Country(models.Model):
else:
return 'no_country'
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = (
'id',
'name',
'code'
)
class SPR(models.Model):
name = models.CharField(_("Name"), max_length=100)
code = models.CharField(_("Admin Code"), max_length=8)
@ -59,6 +70,17 @@ class SPR(models.Model):
else:
return 'no_spr'
class SPRSerializer(serializers.ModelSerializer):
class Meta:
model = SPR
fields = (
'id',
'name',
'code',
'country',
'slug'
)
class City(models.Model):
class Meta:
ordering = ('name',)
@ -79,4 +101,14 @@ class City(models.Model):
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.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.events import Event
from .models.events import Event, Place, PlaceSerializer
from .models.locale import Country ,CountrySerializer, SPR, SPRSerializer, City, CitySerializer
import simplejson
@ -18,3 +22,52 @@ def events_list(request, *args, **kwargs):
'events_list': events,
}
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/
STATIC_URL = '/static/'

View file

@ -10,7 +10,7 @@
<!-- 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://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.css" rel="stylesheet">
<!-- Bootstrap style overrides -->
<style>
body {
@ -60,8 +60,10 @@ body {
<!-- Bootstrap core JavaScript
================================================== -->
<!-- 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 src="../../../../assets/js/vendor/popper.min.js"></script>
<script src="../../../../dist/js/bootstrap.min.js"></script> </body>
<script src="https://unpkg.com/popper.js@1.12.9/dist/umd/popper.js"></script>
<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>

View file

@ -12,3 +12,21 @@
</form>
{% 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>
{% 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>
<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' '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' 'github' %}">GitHub</a><br/>
<br>
</center>

View file

@ -22,6 +22,10 @@ urlpatterns = [
path('', views.home, name='home'),
path('admin/', admin.site.urls),
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('create-team/', views.create_team, name='create-team'),