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.contrib.auth.models import User
|
||||
from .models.locale import Country, SPR, City
|
||||
from .models.profiles import Team, UserProfile
|
||||
from .models.events import Event, EventComment ,CommonEvent, Place, EventPhoto
|
||||
|
||||
from datetime import time
|
||||
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):
|
||||
input_type = 'text'
|
||||
template_name = 'forms/widgets/lookup.html'
|
||||
|
@ -34,7 +18,7 @@ class Lookup(TextInput):
|
|||
checked_attribute = {'selected': True}
|
||||
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)
|
||||
self.source = source
|
||||
self.key = key
|
||||
|
@ -42,14 +26,18 @@ class Lookup(TextInput):
|
|||
|
||||
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
|
||||
|
||||
def format_value(self, value):
|
||||
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:
|
||||
return mark_safe('<option value="">--------</option>')
|
||||
|
||||
|
@ -152,7 +140,7 @@ class TeamForm(forms.ModelForm):
|
|||
model = Team
|
||||
fields = ['name', 'description', 'category', 'city', 'web_url', 'tz']
|
||||
widgets = {
|
||||
'city': Lookup(source='/api/cities/', label='name'),
|
||||
'city': Lookup(source=City),
|
||||
}
|
||||
raw_id_fields = ('city')
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -164,7 +152,7 @@ class NewTeamForm(forms.ModelForm):
|
|||
model = Team
|
||||
fields = ['name', 'description', 'category', 'city', 'web_url', 'tz']
|
||||
widgets = {
|
||||
'city': Lookup(source='/api/cities/', label='name'),
|
||||
'city': Lookup(source=City),
|
||||
}
|
||||
raw_id_fields = ('city')
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -179,7 +167,7 @@ class TeamEventForm(forms.ModelForm):
|
|||
model = Event
|
||||
fields = ['name', 'start_time', 'end_time', 'summary', 'place', 'web_url', 'announce_url', 'tags']
|
||||
widgets = {
|
||||
'place': Lookup(source='/api/places/', label='name'),
|
||||
'place': Lookup(source=Place),
|
||||
'start_time': DateTimeWidget,
|
||||
'end_time': DateTimeWidget
|
||||
}
|
||||
|
@ -211,7 +199,7 @@ class NewPlaceForm(forms.ModelForm):
|
|||
model = Place
|
||||
fields = ['name', 'address', 'city', 'longitude', 'latitude', 'place_url', 'tz']
|
||||
widgets = {
|
||||
'city': Lookup(source='/api/cities/', label='name'),
|
||||
'city': Lookup(source=City),
|
||||
}
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -244,11 +232,11 @@ class SendNotificationsForm(forms.ModelForm):
|
|||
}
|
||||
|
||||
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)
|
||||
class Meta:
|
||||
widgets ={
|
||||
'city': Lookup(source='/api/cities/', label='name'),
|
||||
'city': Lookup(source=City, label='name'),
|
||||
}
|
||||
|
||||
class NewCommonEventForm(forms.ModelForm):
|
||||
|
@ -272,10 +260,10 @@ class NewCommonEventForm(forms.ModelForm):
|
|||
'tags',
|
||||
]
|
||||
widgets ={
|
||||
'country': Lookup(source='/api/countries/', label='name'),
|
||||
'spr': Lookup(source='/api/spr/', label='name'),
|
||||
'city': Lookup(source='/api/cities/', label='name'),
|
||||
'place': Lookup(source='/api/places/', label='name'),
|
||||
'country': Lookup(source=Country, label='name'),
|
||||
'spr': Lookup(source=SPR, label='name'),
|
||||
'city': Lookup(source=City, label='name'),
|
||||
'place': Lookup(source=Place, label='name'),
|
||||
'start_time': DateTimeWidget,
|
||||
'end_time': DateTimeWidget
|
||||
}
|
||||
|
|
|
@ -44,12 +44,14 @@ class Country(models.Model):
|
|||
return 'no_country'
|
||||
|
||||
class CountrySerializer(serializers.ModelSerializer):
|
||||
display = serializers.CharField(source='__str__', read_only=True)
|
||||
class Meta:
|
||||
model = Country
|
||||
fields = (
|
||||
'id',
|
||||
'name',
|
||||
'code'
|
||||
'code',
|
||||
'display',
|
||||
)
|
||||
|
||||
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)
|
||||
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):
|
||||
return u'%s, %s, %s' % (self.name, self.spr.name, self.spr.country.name)
|
||||
|
||||
|
@ -112,6 +121,7 @@ class CitySerializer(serializers.ModelSerializer):
|
|||
fields = (
|
||||
'id',
|
||||
'name',
|
||||
'short_name',
|
||||
'spr',
|
||||
'tz',
|
||||
'slug',
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<select autocomplete="false" id="{{ widget.name }}_select" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %} style="width: 200px">
|
||||
{{ widget.value }}
|
||||
<span class="gt_lookup">
|
||||
<select autocomplete="false" id="{{ widget.name }}_select" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %} >
|
||||
{{ widget.value }}
|
||||
</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() {
|
||||
var self = this;
|
||||
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.searchField.keyup(function() {
|
||||
self.options.search(this.value, function(searchText, results){
|
||||
|
@ -39,8 +39,11 @@
|
|||
});
|
||||
});
|
||||
this.menuWrap.prepend(this.searchField);
|
||||
},
|
||||
open: function(event) {
|
||||
this._super()
|
||||
this.searchField.focus()
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}(jQuery));
|
||||
|
|
|
@ -28,43 +28,11 @@
|
|||
<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/jquery-ui/jquery-ui.min.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/get_together.css' %}" rel="stylesheet">
|
||||
<!-- style overrides -->
|
||||
{%block styles %}{% endblock %}
|
||||
<!-- Bootstrap style overrides -->
|
||||
<style>
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -43,6 +43,7 @@ def home(request, *args, **kwards):
|
|||
near_distance = int(request.GET.get("distance", DEFAULT_NEAR_DISTANCE))
|
||||
context['distance'] = near_distance
|
||||
|
||||
city=None
|
||||
ll = None
|
||||
if "city" in request.GET and request.GET.get("city"):
|
||||
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:
|
||||
ll = g.latlng
|
||||
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:
|
||||
context['geoip_lookup'] = False
|
||||
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)
|
||||
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
|
||||
return render(request, 'get_together/index.html', context)
|
||||
|
||||
|
|
Loading…
Reference in a new issue