diff options
author | Étienne Loks <etienne.loks@proxience.com> | 2014-12-30 16:59:44 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@proxience.com> | 2015-05-06 15:38:32 +0200 |
commit | fea9c2d83c910d890ee1600545a61d88e9df6851 (patch) | |
tree | 52e8f0c1333377628b60f0feefad64861980519f /ishtar_common | |
parent | fa7d0e715228ccec391f6cc87b69abd50f442ef9 (diff) | |
download | Ishtar-fea9c2d83c910d890ee1600545a61d88e9df6851.tar.bz2 Ishtar-fea9c2d83c910d890ee1600545a61d88e9df6851.zip |
Work on new town field (with state and department) - work on new UI for files
Diffstat (limited to 'ishtar_common')
-rw-r--r-- | ishtar_common/forms_common.py | 8 | ||||
-rw-r--r-- | ishtar_common/static/media/style.css | 4 | ||||
-rw-r--r-- | ishtar_common/tasks.py | 35 | ||||
-rw-r--r-- | ishtar_common/templates/blocks/JQueryAdvancedTown.html | 99 | ||||
-rw-r--r-- | ishtar_common/templates/blocks/JQueryAdvancedTown.js | 1 | ||||
-rw-r--r-- | ishtar_common/urls.py | 4 | ||||
-rw-r--r-- | ishtar_common/views.py | 39 | ||||
-rw-r--r-- | ishtar_common/widgets.py | 101 | ||||
-rw-r--r-- | ishtar_common/wizards.py | 7 |
9 files changed, 292 insertions, 6 deletions
diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py index e1a0e9063..23d7126c9 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -54,6 +54,14 @@ def get_town_field(label=_(u"Town"), required=True): validators=[models.valid_id(models.Town)], label=label, help_text=mark_safe(help_text), required=required) +def get_advanced_town_field(label=_(u"Town"), required=True): + # !FIXME hard_link, reverse_lazy doen't seem to work with formsets + return forms.IntegerField( + widget=widgets.JQueryTown("/" + settings.URL_PATH + \ + 'autocomplete-advanced-town'), + validators=[models.valid_id(models.Town)], label=label, + required=required) + def get_person_field(label=_(u"Person"), required=True, person_types=[]): # !FIXME hard_link, reverse_lazy doen't seem to work with formsets widget = None diff --git a/ishtar_common/static/media/style.css b/ishtar_common/static/media/style.css index 55bb95e30..a1b084002 100644 --- a/ishtar_common/static/media/style.css +++ b/ishtar_common/static/media/style.css @@ -906,6 +906,10 @@ a.remove{ width:60px; } +.small, .small input{ + width:60px; +} + #progress{ display:none; position:fixed; diff --git a/ishtar_common/tasks.py b/ishtar_common/tasks.py index a9db26087..a8db97bb1 100644 --- a/ishtar_common/tasks.py +++ b/ishtar_common/tasks.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2013 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> +# Copyright (C) 2013-2014 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -17,13 +17,15 @@ # See the file COPYING for details. +import sys + from django.conf import settings from django.db.models import Q -from geodjangofla.models import Commune -from ishtar_common.models import Town +from ishtar_common.models import Town, Department def load_towns(): + from geodjangofla.models import Commune q = None for dpt_number in settings.ISHTAR_DPTS: query = Q(insee_com__istartswith=dpt_number) @@ -51,3 +53,30 @@ def load_towns(): setattr(town, k, defaults[k]) town.save() return nb, updated + +def update_towns(): + nb, updated = 0, 0 + dpts = dict([(dpt.number, dpt) for dpt in Department.objects.all()]) + q = Town.objects.filter(numero_insee__isnull=False) + total = q.count() + for idx, town in enumerate(q.all()): + sys.stdout.write('\rProcessing... %s/%d' % ( + str(idx+1).zfill(len(str(total))), total)) + if len(town.numero_insee) < 2: + continue + dpt_code = town.numero_insee[:2] + if dpt_code.startswith('9') and int(dpt_code) > 95: + dpt_code = town.numero_insee[:3] + if dpt_code not in dpts: + sys.stdout.write('Missing department with INSEE code: %s' % dpt_code) + continue + if town.departement == dpts[dpt_code]: + continue + if town.departement: + updated += 1 + else: + nb += 1 + town.departement = dpts[dpt_code] + town.save() + sys.stdout.write('\n') + return nb, updated diff --git a/ishtar_common/templates/blocks/JQueryAdvancedTown.html b/ishtar_common/templates/blocks/JQueryAdvancedTown.html new file mode 100644 index 000000000..78d2d7831 --- /dev/null +++ b/ishtar_common/templates/blocks/JQueryAdvancedTown.html @@ -0,0 +1,99 @@ +{% load i18n %}{% load url from future %}</td></tr> +<tr> + <td>{% trans "State" context "Région" %}</td> + <td> + <select id='current-state'> + <option value=''>--------</option>{% for state in states %} + <option value='{{state.number}}'{% if state.number == selected_state %}selected='selected'{% endif %}>{{state}}</option> + {% endfor %}</select> + </td> +</tr> +<tr> + <td>{% trans "Department" %}</td> + <td> + <select id='current-department'> + </select> + </td> +</tr> +<tr class='required'> + <th><label>{% trans "Town" %}</label></th> + <td><input{{attrs_select}}/> +<input type="hidden"{{attrs_hidden}}/> +<script type="text/javascript"><!--// + selected_department = "{{selected_department}}"; + var empty_select = "<option value=''>--------</option>"; + + function update_department_field(){ + var selected_state = $("#current-state").val(); + if (!selected_state){ + $("#current-department").html("<option value=''>"+"{% trans 'Choose a state first' %}"+"</option>"); + $("#current-department").prop('disabled', true); + $('#id_select_{{field_id}}').prop('disabled', true); + return; + } + $.ajax({ + url: "{% url 'department-by-state' %}" + selected_state, + type: 'get', + dataType: 'json', + success: function(data) { + var html = ""; + for (idx in data){ + dpt = data[idx]; + html += "<option value='" + dpt.number + "'"; + if (String(dpt.number) == String(selected_department)){ + html += " selected='selected'"; + } + html += ">" + dpt.value + "</option>"; + } + $("#current-department").html(html); + $("#current-department").prop('disabled', false); + update_search_town(); + } + }); + } + + function update_search_town(){ + selected_department = $("#current-department").val(); + if (selected_department){ + $("#id_select_{{field_id}}").autocomplete( "option", "source", {{source}}+selected_department); + $('#id_select_{{field_id}}').prop('disabled', false); + } else { + $('#id_select_{{field_id}}').prop('disabled', true); + } + } + + function empty_town(){ + $('#id_{{field_id}}').val(null); + $('#id_select_{{field_id}}').val(null); + } + + $(function() { + update_department_field(); + + $("#current-state").change(function(){ + empty_town(); + update_department_field(); + }); + + $("#current-department").change(function(){ + empty_town(); + update_search_town(); + }); + + $("#id_select_{{field_id}}").autocomplete({ + source: {{source}}, + select: function( event, ui ) { + if(ui.item){ + $('#id_{{field_id}}').val(ui.item.id); + } else { + $('#id_{{field_id}}').val(null); + } + }, + minLength: 2{% if options %}, + {{options}} + {% endif %} + }); + + $('#id_select_{{field_id}}').live('click', empty_town); + +});//--></script> diff --git a/ishtar_common/templates/blocks/JQueryAdvancedTown.js b/ishtar_common/templates/blocks/JQueryAdvancedTown.js new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ishtar_common/templates/blocks/JQueryAdvancedTown.js @@ -0,0 +1 @@ + diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py index d31d0d359..0c4059eeb 100644 --- a/ishtar_common/urls.py +++ b/ishtar_common/urls.py @@ -98,8 +98,12 @@ urlpatterns += patterns('ishtar_common.views', name='get-person'), url(r'show-person(?:/(?P<pk>.+))?/(?P<type>.+)?$', 'show_person', name='show-person'), + url(r'department-by-state/(?P<state_id>.+)?$', 'department_by_state', + name='department-by-state'), url(r'autocomplete-town/?$', 'autocomplete_town', name='autocomplete-town'), + url(r'autocomplete-advanced-town/(?P<department_id>[0-9]+[ABab]?)?$', + 'autocomplete_advanced_town', name='autocomplete-advanced-town'), url(r'autocomplete-department/?$', 'autocomplete_department', name='autocomplete-department'), url(r'new-author/(?:(?P<parent_name>[^/]+)/)?(?:(?P<limits>[^/]+)/)?$', diff --git a/ishtar_common/views.py b/ishtar_common/views.py index a8ab91fb9..66b488254 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -202,6 +202,45 @@ def autocomplete_town(request): for town in towns]) return HttpResponse(data, mimetype='text/plain') +def autocomplete_advanced_town(request, department_id=None, state_id=None): + if not request.GET.get('term'): + return HttpResponse(mimetype='text/plain') + q = request.GET.get('term') + q = unicodedata.normalize("NFKD", q).encode('ascii','ignore') + query = Q() + for q in q.split(' '): + extra = Q(name__icontains=q) + if settings.COUNTRY == 'fr': + extra = extra | Q(numero_insee__istartswith=q) + if not department_id: + extra = extra | Q(departement__label__istartswith=q) + query = query & extra + if department_id: + query = query & Q(departement__number__iexact=department_id) + if state_id: + query = query & Q(departement__state__number__iexact=state_id) + limit = 20 + towns = models.Town.objects.filter(query)[:limit] + result = [] + for town in towns: + val = town.name + if hasattr(town, 'numero_insee'): + val += " (%s)" % town.numero_insee + result.append({'id':town.pk, 'value':val}) + data = json.dumps(result) + return HttpResponse(data, mimetype='text/plain') + +def department_by_state(request, state_id=''): + if not state_id: + data = [] + else: + departments = models.Department.objects.filter(state__number=state_id) + data = json.dumps([{'id':department.pk, 'number':department.number, + 'value':unicode(department)} + for department in departments]) + return HttpResponse(data, mimetype='text/plain') + + from types import NoneType def format_val(val): diff --git a/ishtar_common/widgets.py b/ishtar_common/widgets.py index 91594edbd..ba7e61e46 100644 --- a/ishtar_common/widgets.py +++ b/ishtar_common/widgets.py @@ -34,7 +34,7 @@ from django.utils.safestring import mark_safe from django.utils.simplejson import JSONEncoder from django.utils.translation import ugettext_lazy as _ -import models +from ishtar_common import models reverse_lazy = lazy(reverse, unicode) @@ -283,6 +283,105 @@ class JQueryAutoComplete(forms.TextInput): } return html +class JQueryTown(forms.TextInput): + """ + Town fields whith state and department pre-selections + """ + + def __init__(self, source, options={}, + attrs={}, new=False, limit={}): + self.options = None + self.attrs = {} + self.source = source + if len(options) > 0: + self.options = JSONEncoder().encode(options) + self.attrs.update(attrs) + self.new = new + self.limit = limit + + @classmethod + def encode_source(cls, source): + encoded_src = '' + if isinstance(source, list): + encoded_src = JSONEncoder().encode(source) + elif isinstance(source, str) \ + or isinstance(source, unicode): + src = escape(source) + if not src.endswith('/'): + src += "/" + encoded_src = "'%s'" % src + else: + try: + src = unicode(source) + if not src.endswith('/'): + src += "/" + encoded_src = "'%s'" % src + except: + raise ValueError('source type is not valid') + return encoded_src + + def render(self, name, value=None, attrs=None): + attrs_hidden = self.build_attrs(attrs, name=name) + attrs_select = self.build_attrs(attrs) + attrs_select['placeholder'] = _(u"Search...") + selected = '' + selected_state = '' + selected_department = '' + if value: + hiddens = [] + selects = [] + if type(value) not in (list, tuple): + values = unicode(escape(smart_unicode(value))) + values = values.replace('[', '').replace(']', '') + values = values.split(',') + else: + values = [] + for v in value: + values += v.split(',') + for v in values: + if not v: + continue + hiddens.append(v) + selects.append(v) + try: + item = models.Town.objects.get(pk=v) + selects[-1] = unicode(item) + if item.departement: + selected_department = item.departement.number + if item.departement.state: + selected_state = item.departement.state.number + selected = item.pk + except (models.Town.DoesNotExist, ValueError): + selects.pop() + hiddens.pop() + if hiddens and selects: + attrs_hidden['value'] = hiddens[0] + attrs_select['value'] = selects[0] + if not self.attrs.has_key('id'): + attrs_hidden['id'] = 'id_%s' % name + attrs_select['id'] = 'id_select_%s' % name + if 'class' not in attrs_select: + attrs_select['class'] = 'autocomplete' + + source = self.encode_source(self.source) + dct = {'source':mark_safe(source), + 'selected':selected, + 'safe_field_id':slugify(name).replace('-', '_'), + 'field_id':name} + if self.options: + dct['options'] = mark_safe('%s' % self.options) + + dct.update({'attrs_select':mark_safe(flatatt(attrs_select)), + 'attrs_hidden':mark_safe(flatatt(attrs_hidden)), + 'name':name, + 'states':models.State.objects.all().order_by('label'), + 'selected_department':selected_department, + 'selected_state':selected_state + }) + html = loader.get_template('blocks/JQueryAdvancedTown.html').render( + Context(dct)) + return html + class JQueryPersonOrganization(forms.TextInput): """ Complex widget which manage: diff --git a/ishtar_common/wizards.py b/ishtar_common/wizards.py index 190a7fc86..2ad3635d7 100644 --- a/ishtar_common/wizards.py +++ b/ishtar_common/wizards.py @@ -95,8 +95,11 @@ class Wizard(NamedUrlWizardView): def get_template_names(self): templates = ['ishtar/wizard/default_wizard.html'] current_step = self.steps.current - if current_step in self.wizard_templates: - templates = [self.wizard_templates[current_step]] + templates + wizard_templates = dict([ + (key % {'url_name':self.url_name}, self.wizard_templates[key]) + for key in self.wizard_templates]) + if current_step in wizard_templates: + templates = [wizard_templates[current_step]] + templates elif current_step == self.steps.last: templates = ['ishtar/wizard/confirm_wizard.html'] + templates return templates |