Use JQuery for dynamic lookups of form fields instead of using a large <select> list
This commit is contained in:
parent
816e770ccf
commit
c2195f22ae
11 changed files with 219 additions and 7 deletions
|
@ -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'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
5
events/templates/forms/widgets/lookup.html
Normal file
5
events/templates/forms/widgets/lookup.html
Normal 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" />
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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/'
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
<a class="btn btn-primary" href="{% url 'social:begin' 'google-oauth2' %}">Google</a>
|
||||||
<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>
|
<a class="btn btn-primary" href="{% url 'social:begin' 'twitter' %}">Twitter</a>
|
||||||
|
-->
|
||||||
<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>
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
Loading…
Reference in a new issue