Improve Lookup field to be more intuitive and show initial value. Move css out of base.html and into new static get_together.css. Fixes #57
This commit is contained in:
parent
80b6374f4b
commit
3d5046e0c4
8 changed files with 101 additions and 73 deletions
|
@ -4,29 +4,13 @@ from django.forms.widgets import TextInput, Media
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from .models.locale import Country, SPR, City
|
||||||
from .models.profiles import Team, UserProfile
|
from .models.profiles import Team, UserProfile
|
||||||
from .models.events import Event, EventComment ,CommonEvent, Place, EventPhoto
|
from .models.events import Event, EventComment ,CommonEvent, Place, EventPhoto
|
||||||
|
|
||||||
from datetime import time
|
from datetime import time
|
||||||
from time import strptime, strftime
|
from time import strptime, strftime
|
||||||
|
|
||||||
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(TextInput):
|
class Lookup(TextInput):
|
||||||
input_type = 'text'
|
input_type = 'text'
|
||||||
template_name = 'forms/widgets/lookup.html'
|
template_name = 'forms/widgets/lookup.html'
|
||||||
|
@ -34,7 +18,7 @@ class Lookup(TextInput):
|
||||||
checked_attribute = {'selected': True}
|
checked_attribute = {'selected': True}
|
||||||
option_inherits_attrs = False
|
option_inherits_attrs = False
|
||||||
|
|
||||||
def __init__(self, source='#', key="id", label="name", attrs=None):
|
def __init__(self, source, key="id", label='__str__', attrs=None):
|
||||||
super().__init__(attrs)
|
super().__init__(attrs)
|
||||||
self.source = source
|
self.source = source
|
||||||
self.key = key
|
self.key = key
|
||||||
|
@ -42,14 +26,18 @@ class Lookup(TextInput):
|
||||||
|
|
||||||
def get_context(self, name, value, attrs):
|
def get_context(self, name, value, attrs):
|
||||||
context = super().get_context(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
|
return context
|
||||||
|
|
||||||
def format_value(self, value):
|
def format_value(self, value):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
return mark_safe('<option value="%s">%s</option>' % (value, _('No change')))
|
lookup_query = {self.key: value}
|
||||||
|
lookup_object = self.source.objects.get(**lookup_query)
|
||||||
|
lookup_field = getattr(lookup_object, self.label)
|
||||||
|
if callable(lookup_field):
|
||||||
|
lookup_value = lookup_field()
|
||||||
|
else:
|
||||||
|
lookup_value = lookup_field
|
||||||
|
return mark_safe('<option value="%s">%s</option>' % (value, lookup_value))
|
||||||
else:
|
else:
|
||||||
return mark_safe('<option value="">--------</option>')
|
return mark_safe('<option value="">--------</option>')
|
||||||
|
|
||||||
|
@ -152,7 +140,7 @@ class TeamForm(forms.ModelForm):
|
||||||
model = Team
|
model = Team
|
||||||
fields = ['name', 'description', 'category', 'city', 'web_url', 'tz']
|
fields = ['name', 'description', 'category', 'city', 'web_url', 'tz']
|
||||||
widgets = {
|
widgets = {
|
||||||
'city': Lookup(source='/api/cities/', label='name'),
|
'city': Lookup(source=City),
|
||||||
}
|
}
|
||||||
raw_id_fields = ('city')
|
raw_id_fields = ('city')
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -164,7 +152,7 @@ class NewTeamForm(forms.ModelForm):
|
||||||
model = Team
|
model = Team
|
||||||
fields = ['name', 'description', 'category', 'city', 'web_url', 'tz']
|
fields = ['name', 'description', 'category', 'city', 'web_url', 'tz']
|
||||||
widgets = {
|
widgets = {
|
||||||
'city': Lookup(source='/api/cities/', label='name'),
|
'city': Lookup(source=City),
|
||||||
}
|
}
|
||||||
raw_id_fields = ('city')
|
raw_id_fields = ('city')
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -179,7 +167,7 @@ class TeamEventForm(forms.ModelForm):
|
||||||
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 = {
|
widgets = {
|
||||||
'place': Lookup(source='/api/places/', label='name'),
|
'place': Lookup(source=Place),
|
||||||
'start_time': DateTimeWidget,
|
'start_time': DateTimeWidget,
|
||||||
'end_time': DateTimeWidget
|
'end_time': DateTimeWidget
|
||||||
}
|
}
|
||||||
|
@ -211,7 +199,7 @@ class NewPlaceForm(forms.ModelForm):
|
||||||
model = Place
|
model = Place
|
||||||
fields = ['name', 'address', 'city', 'longitude', 'latitude', 'place_url', 'tz']
|
fields = ['name', 'address', 'city', 'longitude', 'latitude', 'place_url', 'tz']
|
||||||
widgets = {
|
widgets = {
|
||||||
'city': Lookup(source='/api/cities/', label='name'),
|
'city': Lookup(source=City),
|
||||||
}
|
}
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -244,11 +232,11 @@ class SendNotificationsForm(forms.ModelForm):
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchForm(forms.Form):
|
class SearchForm(forms.Form):
|
||||||
city = forms.IntegerField(required=False, widget=Lookup(source='/api/cities/', label='name'))
|
city = forms.IntegerField(required=False, widget=Lookup(source=City, label='name'))
|
||||||
distance = forms.IntegerField(label=_("Distance(km)"), required=True)
|
distance = forms.IntegerField(label=_("Distance(km)"), required=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
widgets ={
|
widgets ={
|
||||||
'city': Lookup(source='/api/cities/', label='name'),
|
'city': Lookup(source=City, label='name'),
|
||||||
}
|
}
|
||||||
|
|
||||||
class NewCommonEventForm(forms.ModelForm):
|
class NewCommonEventForm(forms.ModelForm):
|
||||||
|
@ -272,10 +260,10 @@ class NewCommonEventForm(forms.ModelForm):
|
||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
widgets ={
|
widgets ={
|
||||||
'country': Lookup(source='/api/countries/', label='name'),
|
'country': Lookup(source=Country, label='name'),
|
||||||
'spr': Lookup(source='/api/spr/', label='name'),
|
'spr': Lookup(source=SPR, label='name'),
|
||||||
'city': Lookup(source='/api/cities/', label='name'),
|
'city': Lookup(source=City, label='name'),
|
||||||
'place': Lookup(source='/api/places/', label='name'),
|
'place': Lookup(source=Place, label='name'),
|
||||||
'start_time': DateTimeWidget,
|
'start_time': DateTimeWidget,
|
||||||
'end_time': DateTimeWidget
|
'end_time': DateTimeWidget
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,12 +44,14 @@ class Country(models.Model):
|
||||||
return 'no_country'
|
return 'no_country'
|
||||||
|
|
||||||
class CountrySerializer(serializers.ModelSerializer):
|
class CountrySerializer(serializers.ModelSerializer):
|
||||||
|
display = serializers.CharField(source='__str__', read_only=True)
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Country
|
model = Country
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
'code'
|
'code',
|
||||||
|
'display',
|
||||||
)
|
)
|
||||||
|
|
||||||
class SPR(models.Model):
|
class SPR(models.Model):
|
||||||
|
@ -94,6 +96,13 @@ class City(models.Model):
|
||||||
longitude = models.FloatField(help_text=_('Longitude in Degrees East'), null=True, blank=True)
|
longitude = models.FloatField(help_text=_('Longitude in Degrees East'), null=True, blank=True)
|
||||||
latitude = models.FloatField(help_text=_('Latitude in Degrees North'), null=True, blank=True)
|
latitude = models.FloatField(help_text=_('Latitude in Degrees North'), null=True, blank=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def short_name(self):
|
||||||
|
if self.spr.country.name == 'United States':
|
||||||
|
return u'%s, %s' % (self.name, self.spr.name)
|
||||||
|
else:
|
||||||
|
return u'%s, %s' % (self.name, self.spr.country.name)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return u'%s, %s, %s' % (self.name, self.spr.name, self.spr.country.name)
|
return u'%s, %s, %s' % (self.name, self.spr.name, self.spr.country.name)
|
||||||
|
|
||||||
|
@ -112,6 +121,7 @@ class CitySerializer(serializers.ModelSerializer):
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
|
'short_name',
|
||||||
'spr',
|
'spr',
|
||||||
'tz',
|
'tz',
|
||||||
'slug',
|
'slug',
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
<select autocomplete="false" id="{{ widget.name }}_select" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %} style="width: 200px">
|
<span class="gt_lookup">
|
||||||
{{ widget.value }}
|
<select autocomplete="false" id="{{ widget.name }}_select" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %} >
|
||||||
|
{{ widget.value }}
|
||||||
</select>
|
</select>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
|
40
get_together/static/css/get_together.css
Normal file
40
get_together/static/css/get_together.css
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
body {
|
||||||
|
padding-top: 5rem;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
padding-top: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
border-top: #cfcfcf 1px solid;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #aaaaaa;
|
||||||
|
}
|
||||||
|
.footer ul {
|
||||||
|
display: inline;
|
||||||
|
list-style-type: none;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.footer ul li {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.footer a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.starter-template {
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.ui-selectmenu-menu .ui-menu {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
.gt_lookup .ui-selectmenu-icon.ui-icon {
|
||||||
|
background-position: 0px 0px;
|
||||||
|
background-image: url(/static/img/search_icon.png);
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
BIN
get_together/static/img/search_icon.png
Normal file
BIN
get_together/static/img/search_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 783 B |
7
get_together/static/js/jquery-ui-lookup.js
vendored
7
get_together/static/js/jquery-ui-lookup.js
vendored
|
@ -22,7 +22,7 @@
|
||||||
_drawSearch: function() {
|
_drawSearch: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var selectField = this.element[0];
|
var selectField = this.element[0];
|
||||||
this.searchField = $('<input type="text" placeholder="Search" style="width: 100%;">');
|
this.searchField = $('<input type="text" class="ui-lookup-search border border-primary" placeholder="Search" style="width: 100%;">');
|
||||||
//this._addClass( this.searchField, "ui-selectmenu-menu", "ui-front" );
|
//this._addClass( this.searchField, "ui-selectmenu-menu", "ui-front" );
|
||||||
this.searchField.keyup(function() {
|
this.searchField.keyup(function() {
|
||||||
self.options.search(this.value, function(searchText, results){
|
self.options.search(this.value, function(searchText, results){
|
||||||
|
@ -39,8 +39,11 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.menuWrap.prepend(this.searchField);
|
this.menuWrap.prepend(this.searchField);
|
||||||
|
},
|
||||||
|
open: function(event) {
|
||||||
|
this._super()
|
||||||
|
this.searchField.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}(jQuery));
|
}(jQuery));
|
||||||
|
|
|
@ -28,43 +28,11 @@
|
||||||
<link rel="stylesheet" href="{% static 'css/bootstrap/css/bootstrap.min.css' %}">
|
<link rel="stylesheet" href="{% static 'css/bootstrap/css/bootstrap.min.css' %}">
|
||||||
<link href="{% static 'js/tether/css/tether.min.css' %}" rel="stylesheet">
|
<link href="{% static 'js/tether/css/tether.min.css' %}" rel="stylesheet">
|
||||||
<link href="{% static 'js/jquery-ui/jquery-ui.min.css' %}" rel="stylesheet">
|
<link href="{% static 'js/jquery-ui/jquery-ui.min.css' %}" rel="stylesheet">
|
||||||
|
<link href="{% static 'css/get_together.css' %}" rel="stylesheet">
|
||||||
|
<!-- style overrides -->
|
||||||
{%block styles %}{% endblock %}
|
{%block styles %}{% endblock %}
|
||||||
<!-- Bootstrap style overrides -->
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
</style>
|
||||||
padding-top: 5rem;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
padding-top: 1em;
|
|
||||||
padding-bottom: 1em;
|
|
||||||
border-top: #cfcfcf 1px solid;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
color: #aaaaaa;
|
|
||||||
}
|
|
||||||
.footer ul {
|
|
||||||
display: inline;
|
|
||||||
list-style-type: none;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
.footer ul li {
|
|
||||||
display: inline;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
.footer a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
.starter-template {
|
|
||||||
padding: 3rem 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
.ui-selectmenu-menu .ui-menu {
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -43,6 +43,7 @@ def home(request, *args, **kwards):
|
||||||
near_distance = int(request.GET.get("distance", DEFAULT_NEAR_DISTANCE))
|
near_distance = int(request.GET.get("distance", DEFAULT_NEAR_DISTANCE))
|
||||||
context['distance'] = near_distance
|
context['distance'] = near_distance
|
||||||
|
|
||||||
|
city=None
|
||||||
ll = None
|
ll = None
|
||||||
if "city" in request.GET and request.GET.get("city"):
|
if "city" in request.GET and request.GET.get("city"):
|
||||||
context['city_search'] = True
|
context['city_search'] = True
|
||||||
|
@ -56,6 +57,22 @@ def home(request, *args, **kwards):
|
||||||
if g.latlng is not None and g.latlng[0] is not None and g.latlng[1] is not None:
|
if g.latlng is not None and g.latlng[0] is not None and g.latlng[1] is not None:
|
||||||
ll = g.latlng
|
ll = g.latlng
|
||||||
context['geoip_lookup'] = True
|
context['geoip_lookup'] = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
city_distance = 1 #km
|
||||||
|
while city is None and city_distance < 100:
|
||||||
|
minlat = ll[0]-(city_distance/KM_PER_DEGREE_LAT)
|
||||||
|
maxlat = ll[0]+(city_distance/KM_PER_DEGREE_LAT)
|
||||||
|
minlng = ll[1]-(city_distance/(KM_PER_DEGREE_LNG*math.cos(math.radians(ll[0]))))
|
||||||
|
maxlng = ll[1]+(city_distance/(KM_PER_DEGREE_LNG*math.cos(math.radians(ll[0]))))
|
||||||
|
nearby_cities = City.objects.filter(latitude__gte=minlat, latitude__lte=maxlat, longitude__gte=minlng, longitude__lte=maxlng)
|
||||||
|
if len(nearby_cities) == 0:
|
||||||
|
city_distance += 1
|
||||||
|
else:
|
||||||
|
city = nearby_cities[0]
|
||||||
|
except:
|
||||||
|
pass # City lookup failed
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
context['geoip_lookup'] = False
|
context['geoip_lookup'] = False
|
||||||
print("Geocoder lookup failed for %s" % client_ip, err)
|
print("Geocoder lookup failed for %s" % client_ip, err)
|
||||||
|
@ -84,7 +101,7 @@ def home(request, *args, **kwards):
|
||||||
print("Error looking up nearby teams and events", err)
|
print("Error looking up nearby teams and events", err)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
search_form = SearchForm(initial={'distance': near_distance})
|
search_form = SearchForm(initial={'city': city.id, 'distance': near_distance})
|
||||||
context['search_form'] = search_form
|
context['search_form'] = search_form
|
||||||
return render(request, 'get_together/index.html', context)
|
return render(request, 'get_together/index.html', context)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue