diff options
Diffstat (limited to 'ishtar_common')
31 files changed, 2984 insertions, 327 deletions
| diff --git a/ishtar_common/data_importer.py b/ishtar_common/data_importer.py index f5e70e0ee..a7c32e6a8 100644 --- a/ishtar_common/data_importer.py +++ b/ishtar_common/data_importer.py @@ -63,14 +63,15 @@ class ImportFormater(object):      def report_error(self, *args):          return -    def init(self, vals, output=None): +    def init(self, vals, output=None, choose_default=False):          try:              lst = iter(self.formater)          except TypeError:              lst = [self.formater]          for formater in lst:              if formater: -                formater.check(vals, output) +                formater.check(vals, output, self.comment, +                               choose_default=choose_default)      def post_process(self, obj, context, value, owner=None):          raise NotImplemented() @@ -92,9 +93,19 @@ class Formater(object):      def format(self, value):          return value -    def check(self, values, output=None): +    def check(self, values, output=None, comment='', choose_default=False):          return +class ChoiceChecker(object): +    def report_new(self, comment): +        if not self.new_keys: +            return +        msg = u"For \"%s\" these new associations have been made:\n" % comment +        sys.stderr.write(msg.encode('utf-8')) +        for k in self.new_keys: +            msg = u'"%s";"%s"\n' % (k, self.new_keys[k]) +            sys.stderr.write(msg.encode('utf-8')) +  class UnicodeFormater(Formater):      def __init__(self, max_length, clean=False, re_filter=None, notnull=False,                   db_target=None): @@ -183,7 +194,7 @@ class IntegerFormater(Formater):              raise ValueError(_(u"\"%(value)s\" is not an integer") % {                                                                   'value':value}) -class StrChoiceFormater(Formater): +class StrChoiceFormater(Formater, ChoiceChecker):      def __init__(self, choices, strict=False, equiv_dict={}, model=None,                   cli=False, many_split='', db_target=None):          self.choices = list(choices) @@ -194,6 +205,7 @@ class StrChoiceFormater(Formater):          self.db_target = db_target          self.create = False          self.missings = set() +        self.new_keys = {}          self.many_split = many_split          for key, value in self.choices:              value = unicode(value) @@ -219,8 +231,9 @@ class StrChoiceFormater(Formater):      def prepare(self, value):          return unicode(value).strip() -    def _get_choices(self): -        msgstr = unicode(_(u"Choice for \"%s\" is not available. "\ +    def _get_choices(self, comment=''): +        msgstr = comment + u" - " +        msgstr += unicode(_(u"Choice for \"%s\" is not available. "\                             u"Which one is relevant?\n"))          idx = -1          for idx, choice in enumerate(self.choices): @@ -233,8 +246,8 @@ class StrChoiceFormater(Formater):          msgstr += unicode(_(u"%d. None of the above - skip")) % idx + u"\n"          return msgstr, idx -    def check(self, values, output=None): -        if not output or output == 'silent': +    def check(self, values, output=None, comment='', choose_default=False): +        if (not output or output == 'silent') and not choose_default:              return          if self.many_split:              new_values = [] @@ -247,14 +260,18 @@ class StrChoiceFormater(Formater):              value = self.prepare(value)              if value in self.equiv_dict:                  continue -            if output != 'cli': +            if output != 'cli' and not choose_default:                  self.missings.add(value)                  continue -            msgstr, idx = self._get_choices() +            msgstr, idx = self._get_choices(comment)              res = None +            if choose_default: +                res = 1              while res not in range(1, idx+1): -                sys.stdout.write(msgstr % value) -                res = raw_input(">>> ") +                msg = msgstr % value +                sys.stdout.write(msg.encode('utf-8')) +                sys.stdout.write("\n>>> ") +                res = raw_input()                  try:                      res = int(res)                  except ValueError: @@ -266,10 +283,12 @@ class StrChoiceFormater(Formater):                      v = self.model.objects.get(pk=v)                  self.equiv_dict[value] = v                  self.add_key(v, value) +                self.new_keys[value] = v              elif self.create and res == len(self.choices):                  self.equiv_dict[value] = self.new(base_value)                  self.choices.append((self.equiv_dict[value].pk,                                       unicode(self.equiv_dict[value]))) +                self.new_keys[value] = unicode(self.equiv_dict[value])              else:                  self.equiv_dict[value] = None          if output == 'db' and self.db_target: @@ -283,6 +302,8 @@ class StrChoiceFormater(Formater):                          TargetKey.objects.create(**q)                      except IntegrityError:                          pass +        if output == 'cli': +            self.report_new(comment)      def new(self, value):          return @@ -308,6 +329,7 @@ class TypeFormater(StrChoiceFormater):          self.db_target = db_target          self.missings = set()          self.equiv_dict, self.choices = {}, [] +        self.new_keys = {}          for item in model.objects.all():              self.choices.append((item.pk, unicode(item)))              for key in item.get_keys(): @@ -332,21 +354,25 @@ class TypeFormater(StrChoiceFormater):          return self.model.objects.create(**values)  class DateFormater(Formater): -    def __init__(self, date_format="%d/%m/%Y", db_target=None): -        self.date_format = date_format +    def __init__(self, date_formats=["%d/%m/%Y"], db_target=None): +        self.date_formats = date_formats +        if type(date_formats) not in (list, tuple): +            self.date_formats = [self.date_formats]          self.db_target = db_target      def format(self, value):          value = value.strip()          if not value:              return -        try: -            return datetime.datetime.strptime(value, self.date_format).date() -        except: -            raise ValueError(_(u"\"%(value)s\" is not a valid date") % { +        for date_format in self.date_formats: +            try: +                return datetime.datetime.strptime(value, date_format).date() +            except: +                continue +        raise ValueError(_(u"\"%(value)s\" is not a valid date") % {                                                             'value':value}) -class StrToBoolean(Formater): +class StrToBoolean(Formater, ChoiceChecker):      def __init__(self, choices={}, cli=False, strict=False, db_target=None):          self.dct = copy.copy(choices)          self.cli = cli @@ -367,6 +393,7 @@ class StrToBoolean(Formater):                  else:                      v = None                  self.dct[value] = v +        self.new_keys = {}      def prepare(self, value):          value = unicode(value).strip() @@ -374,10 +401,11 @@ class StrToBoolean(Formater):              value = slugify(value)          return value -    def check(self, values, output=None): -        if not output or output == 'silent': +    def check(self, values, output=None, comment='', choose_default=False): +        if (not output or output == 'silent') and not choose_default:              return -        msgstr = unicode(_(u"Choice for \"%s\" is not available. "\ +        msgstr = comment + u" - " +        msgstr += unicode(_(u"Choice for \"%s\" is not available. "\                             u"Which one is relevant?\n"))          msgstr += u"1. True\n"          msgstr += u"2. False\n" @@ -386,13 +414,17 @@ class StrToBoolean(Formater):              value = self.prepare(value)              if value in self.dct:                  continue -            if not self.cli: +            if output != 'cli' and not choose_default:                  self.missings.add(value)                  continue              res = None +            if choose_default: +                res = 1              while res not in range(1, 4): -                sys.stdout.write(msgstr % value) -                res = raw_input(">>> ") +                msg = msgstr % value +                sys.stdout.write(msg.encode('utf-8')) +                sys.stdout.write("\n>>> ") +                res = raw_input()                  try:                      res = int(res)                  except ValueError: @@ -411,6 +443,9 @@ class StrToBoolean(Formater):                      models.TargetKey.objects.create(**q)                  except IntegrityError:                      pass +            self.new_keys[value] = unicode(self.dct[value]) +        if output == 'cli': +            self.report_new(comment)      def format(self, value):          value = self.prepare(value) @@ -419,13 +454,22 @@ class StrToBoolean(Formater):  logger = logging.getLogger(__name__) +def get_object_from_path(obj, path): +    for k in path.split('__')[:-1]: +        if not hasattr(obj, k): +            return +        obj = getattr(obj, k) +    return obj +  class Importer(object):      DESC = ""      LINE_FORMAT = []      OBJECT_CLS = None      IMPORTED_LINE_FIELD = None      UNICITY_KEYS = [] +    EXTRA_DEFAULTS = {}      DEFAULTS = {} +    STR_CUT = {}      ERRORS = {          'header_check':_(u"The given file is not correct. Check the file "                    u"format. If you use a CSV file: check that column separator " @@ -459,6 +503,12 @@ class Importer(object):          self.line_format = copy.copy(self.LINE_FORMAT)          self.import_instance = import_instance          self._defaults = self.DEFAULTS.copy() +        # EXTRA_DEFAULTS are for multiple inheritance +        if self.EXTRA_DEFAULTS: +            for k in self.EXTRA_DEFAULTS: +                if k not in self._defaults: +                    self._defaults[k] = {} +                self._defaults[k].update(self.EXTRA_DEFAULTS[k])          self.history_modifier = history_modifier          self.output = output          if not self.history_modifier: @@ -469,7 +519,10 @@ class Importer(object):                  self.history_modifier = User.objects.filter(                                  is_superuser=True).order_by('pk')[0] -    def initialize(self, table, output='silent'): +    def post_processing(self, item, data): +        return item + +    def initialize(self, table, output='silent', choose_default=False):          """          copy vals in columns and initialize formaters          * output: @@ -491,11 +544,11 @@ class Importer(object):                  vals[idx_col].append(val)          for idx, formater in enumerate(self.line_format):              if formater and idx < len(vals): -                formater.init(vals[idx], output) +                formater.init(vals[idx], output, choose_default=choose_default) -    def importation(self, table, initialize=True): +    def importation(self, table, initialize=True, choose_default=False):          if initialize: -            self.initialize(table, self.output) +            self.initialize(table, self.output, choose_default=choose_default)          self._importation(table)      @classmethod @@ -560,10 +613,10 @@ class Importer(object):                      time_by_item = ellapsed/idx_line                      if time_by_item:                          left = ((total - idx_line)*time_by_item).seconds -                txt = "\r* %d/%d" % (idx_line+1, total) +                txt = u"\r* %d/%d" % (idx_line+1, total)                  if left: -                    txt += " (%d seconds left)" % left -                sys.stdout.write(txt) +                    txt += u" (%d seconds left)" % left +                sys.stdout.write(txt.encode('utf-8'))                  sys.stdout.flush()              try:                  self._line_processing(idx_line, line) @@ -623,8 +676,8 @@ class Importer(object):          if 'history_modifier' in \                         self.OBJECT_CLS._meta.get_all_field_names():              data['history_modifier'] = self.history_modifier -        obj, created = self.get_object(self.OBJECT_CLS, data) +        obj, created = self.get_object(self.OBJECT_CLS, data)          if self.import_instance and hasattr(obj, 'imports'):              obj.imports.add(self.import_instance) @@ -669,6 +722,8 @@ class Importer(object):          for formater, val in self._post_processing:              formater.post_process(obj, data, val, owner=self.history_modifier) +        obj = self.post_processing(obj, data) +      def _row_processing(self, c_row, idx_col, idx_line, val, data):          if idx_col >= len(self.line_format):              return @@ -833,12 +888,19 @@ class Importer(object):                                                      self.history_modifier                      data[attribute], created = self.get_object(                                     field_object.rel.to, data[attribute], c_path) +            # default values              path = tuple(path)              if path in self._defaults:                  for k in self._defaults[path]:                      if k not in data or not data[k]:                          data[k] = self._defaults[path][k] +            # pre treatment +            if path in self.STR_CUT: +                for k in self.STR_CUT[path]: +                    if k in data and data[k]: +                        data[k] = unicode(data[k])[:self.STR_CUT[path][k]] +              # filter default values              create_dict = copy.deepcopy(data)              for k in create_dict.keys(): @@ -857,11 +919,7 @@ class Importer(object):                          obj.imports.add(self.import_instance)                  except IntegrityError as e:                      raise IntegrityError(e.message) -                except: -                    q = cls.objects.filter(**create_dict) -                    if not q.count(): -                        raise ImporterError("Erreur d'import %s, contexte : %s"\ -                                                % (unicode(cls), unicode(data))) +                except cls.MultipleObjectsReturned:                      created = False                      obj = cls.objects.filter(**create_dict).all()[0]                  for attr, value in m2ms: @@ -871,6 +929,15 @@ class Importer(object):                      for v in values:                          getattr(obj, attr).add(v)              except IntegrityError as e: +                message = e.message +                try: +                    message = unicode(e.message.decode('utf-8')) +                except (UnicodeDecodeError, UnicodeDecodeError): +                    message = '' +                try: +                    data = unicode(data) +                except UnicodeDecodeError: +                    data = ''                  raise ImporterError(                      "Erreur d'import %s, contexte : %s, erreur : %s" \                      % (unicode(cls), unicode(data), e.message.decode('utf-8'))) diff --git a/ishtar_common/fixtures/initial_dpts-fr.json b/ishtar_common/fixtures/initial_dpts-fr.json new file mode 100644 index 000000000..721a96a8a --- /dev/null +++ b/ishtar_common/fixtures/initial_dpts-fr.json @@ -0,0 +1,1127 @@ +[ +    { +        "pk": 28,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "1",  +            "label": "Guadeloupe" +        } +    },  +    { +        "pk": 33,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "11",  +            "label": "\u00cele-de-France" +        } +    },  +    { +        "pk": 29,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "2",  +            "label": "Martinique" +        } +    },  +    { +        "pk": 34,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "21",  +            "label": "Champagne-Ardenne" +        } +    },  +    { +        "pk": 35,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "22",  +            "label": "Picardie" +        } +    },  +    { +        "pk": 36,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "23",  +            "label": "Haute-Normandie" +        } +    },  +    { +        "pk": 37,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "24",  +            "label": "Centre" +        } +    },  +    { +        "pk": 38,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "25",  +            "label": "Basse-Normandie" +        } +    },  +    { +        "pk": 39,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "26",  +            "label": "Bourgogne" +        } +    },  +    { +        "pk": 30,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "3",  +            "label": "Guyane" +        } +    },  +    { +        "pk": 40,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "31",  +            "label": "Nord-Pas-de-Calais" +        } +    },  +    { +        "pk": 31,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "4",  +            "label": "La R\u00e9union" +        } +    },  +    { +        "pk": 41,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "41",  +            "label": "Lorraine" +        } +    },  +    { +        "pk": 42,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "42",  +            "label": "Alsace" +        } +    },  +    { +        "pk": 43,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "43",  +            "label": "Franche-Comt\u00e9" +        } +    },  +    { +        "pk": 44,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "52",  +            "label": "Pays de la Loire" +        } +    },  +    { +        "pk": 45,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "53",  +            "label": "Bretagne" +        } +    },  +    { +        "pk": 46,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "54",  +            "label": "Poitou-Charentes" +        } +    },  +    { +        "pk": 32,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "6",  +            "label": "Mayotte" +        } +    },  +    { +        "pk": 47,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "72",  +            "label": "Aquitaine" +        } +    },  +    { +        "pk": 48,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "73",  +            "label": "Midi-Pyr\u00e9n\u00e9es" +        } +    },  +    { +        "pk": 49,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "74",  +            "label": "Limousin" +        } +    },  +    { +        "pk": 50,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "82",  +            "label": "Rh\u00f4ne-Alpes" +        } +    },  +    { +        "pk": 51,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "83",  +            "label": "Auvergne" +        } +    },  +    { +        "pk": 52,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "91",  +            "label": "Languedoc-Roussillon" +        } +    },  +    { +        "pk": 53,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "93",  +            "label": "Provence-Alpes-C\u00f4te d'Azur" +        } +    },  +    { +        "pk": 54,  +        "model": "ishtar_common.state",  +        "fields": { +            "number": "94",  +            "label": "Corse" +        } +    },  +    { +        "pk": 1,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 50,  +            "number": "01",  +            "label": "Ain" +        } +    },  +    { +        "pk": 2,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 35,  +            "number": "02",  +            "label": "Aisne" +        } +    },  +    { +        "pk": 3,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 51,  +            "number": "03",  +            "label": "Allier" +        } +    },  +    { +        "pk": 4,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 53,  +            "number": "04",  +            "label": "Alpes-de-Haute-Provence" +        } +    },  +    { +        "pk": 5,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 53,  +            "number": "05",  +            "label": "Hautes-Alpes" +        } +    },  +    { +        "pk": 6,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 53,  +            "number": "06",  +            "label": "Alpes-Maritimes" +        } +    },  +    { +        "pk": 7,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 50,  +            "number": "07",  +            "label": "Ard\u00e8che" +        } +    },  +    { +        "pk": 8,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 34,  +            "number": "08",  +            "label": "Ardennes" +        } +    },  +    { +        "pk": 9,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 48,  +            "number": "09",  +            "label": "Ari\u00e8ge" +        } +    },  +    { +        "pk": 10,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 34,  +            "number": "10",  +            "label": "Aube" +        } +    },  +    { +        "pk": 11,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 52,  +            "number": "11",  +            "label": "Aude" +        } +    },  +    { +        "pk": 12,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 48,  +            "number": "12",  +            "label": "Aveyron" +        } +    },  +    { +        "pk": 13,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 53,  +            "number": "13",  +            "label": "Bouches-du-Rh\u00f4ne" +        } +    },  +    { +        "pk": 14,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 38,  +            "number": "14",  +            "label": "Calvados" +        } +    },  +    { +        "pk": 15,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 51,  +            "number": "15",  +            "label": "Cantal" +        } +    },  +    { +        "pk": 16,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 46,  +            "number": "16",  +            "label": "Charente" +        } +    },  +    { +        "pk": 17,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 46,  +            "number": "17",  +            "label": "Charente-Maritime" +        } +    },  +    { +        "pk": 18,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 37,  +            "number": "18",  +            "label": "Cher" +        } +    },  +    { +        "pk": 19,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 49,  +            "number": "19",  +            "label": "Corr\u00e8ze" +        } +    },  +    { +        "pk": 22,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 39,  +            "number": "21",  +            "label": "C\u00f4te-d'Or" +        } +    },  +    { +        "pk": 23,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 45,  +            "number": "22",  +            "label": "C\u00f4tes-d'Armor" +        } +    },  +    { +        "pk": 24,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 49,  +            "number": "23",  +            "label": "Creuse" +        } +    },  +    { +        "pk": 25,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 47,  +            "number": "24",  +            "label": "Dordogne" +        } +    },  +    { +        "pk": 26,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 43,  +            "number": "25",  +            "label": "Doubs" +        } +    },  +    { +        "pk": 27,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 50,  +            "number": "26",  +            "label": "Dr\u00f4me" +        } +    },  +    { +        "pk": 28,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 36,  +            "number": "27",  +            "label": "Eure" +        } +    },  +    { +        "pk": 29,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 37,  +            "number": "28",  +            "label": "Eure-et-Loir" +        } +    },  +    { +        "pk": 30,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 45,  +            "number": "29",  +            "label": "Finist\u00e8re" +        } +    },  +    { +        "pk": 20,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 54,  +            "number": "2A",  +            "label": "Corse-du-Sud" +        } +    },  +    { +        "pk": 21,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 54,  +            "number": "2B",  +            "label": "Haute-Corse" +        } +    },  +    { +        "pk": 31,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 52,  +            "number": "30",  +            "label": "Gard" +        } +    },  +    { +        "pk": 32,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 48,  +            "number": "31",  +            "label": "Haute-Garonne" +        } +    },  +    { +        "pk": 33,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 48,  +            "number": "32",  +            "label": "Gers" +        } +    },  +    { +        "pk": 34,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 47,  +            "number": "33",  +            "label": "Gironde" +        } +    },  +    { +        "pk": 35,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 52,  +            "number": "34",  +            "label": "H\u00e9rault" +        } +    },  +    { +        "pk": 36,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 45,  +            "number": "35",  +            "label": "Ille-et-Vilaine" +        } +    },  +    { +        "pk": 37,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 37,  +            "number": "36",  +            "label": "Indre" +        } +    },  +    { +        "pk": 38,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 37,  +            "number": "37",  +            "label": "Indre-et-Loire" +        } +    },  +    { +        "pk": 39,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 50,  +            "number": "38",  +            "label": "Is\u00e8re" +        } +    },  +    { +        "pk": 40,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 43,  +            "number": "39",  +            "label": "Jura" +        } +    },  +    { +        "pk": 41,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 47,  +            "number": "40",  +            "label": "Landes" +        } +    },  +    { +        "pk": 42,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 37,  +            "number": "41",  +            "label": "Loir-et-Cher" +        } +    },  +    { +        "pk": 43,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 50,  +            "number": "42",  +            "label": "Loire" +        } +    },  +    { +        "pk": 44,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 51,  +            "number": "43",  +            "label": "Haute-Loire" +        } +    },  +    { +        "pk": 45,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 44,  +            "number": "44",  +            "label": "Loire-Atlantique" +        } +    },  +    { +        "pk": 46,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 37,  +            "number": "45",  +            "label": "Loiret" +        } +    },  +    { +        "pk": 47,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 48,  +            "number": "46",  +            "label": "Lot" +        } +    },  +    { +        "pk": 48,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 47,  +            "number": "47",  +            "label": "Lot-et-Garonne" +        } +    },  +    { +        "pk": 49,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 52,  +            "number": "48",  +            "label": "Loz\u00e8re" +        } +    },  +    { +        "pk": 50,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 44,  +            "number": "49",  +            "label": "Maine-et-Loire" +        } +    },  +    { +        "pk": 51,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 38,  +            "number": "50",  +            "label": "Manche" +        } +    },  +    { +        "pk": 52,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 34,  +            "number": "51",  +            "label": "Marne" +        } +    },  +    { +        "pk": 53,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 34,  +            "number": "52",  +            "label": "Haute-Marne" +        } +    },  +    { +        "pk": 54,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 44,  +            "number": "53",  +            "label": "Mayenne" +        } +    },  +    { +        "pk": 55,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 41,  +            "number": "54",  +            "label": "Meurthe-et-Moselle" +        } +    },  +    { +        "pk": 56,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 41,  +            "number": "55",  +            "label": "Meuse" +        } +    },  +    { +        "pk": 57,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 45,  +            "number": "56",  +            "label": "Morbihan" +        } +    },  +    { +        "pk": 58,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 41,  +            "number": "57",  +            "label": "Moselle" +        } +    },  +    { +        "pk": 59,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 39,  +            "number": "58",  +            "label": "Ni\u00e8vre" +        } +    },  +    { +        "pk": 60,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 40,  +            "number": "59",  +            "label": "Nord" +        } +    },  +    { +        "pk": 61,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 35,  +            "number": "60",  +            "label": "Oise" +        } +    },  +    { +        "pk": 62,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 38,  +            "number": "61",  +            "label": "Orne" +        } +    },  +    { +        "pk": 63,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 40,  +            "number": "62",  +            "label": "Pas-de-Calais" +        } +    },  +    { +        "pk": 64,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 51,  +            "number": "63",  +            "label": "Puy-de-D\u00f4me" +        } +    },  +    { +        "pk": 65,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 47,  +            "number": "64",  +            "label": "Pyr\u00e9n\u00e9es-Atlantiques" +        } +    },  +    { +        "pk": 66,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 48,  +            "number": "65",  +            "label": "Hautes-Pyr\u00e9n\u00e9es" +        } +    },  +    { +        "pk": 67,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 52,  +            "number": "66",  +            "label": "Pyr\u00e9n\u00e9es-Orientales" +        } +    },  +    { +        "pk": 68,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 42,  +            "number": "67",  +            "label": "Bas-Rhin" +        } +    },  +    { +        "pk": 69,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 42,  +            "number": "68",  +            "label": "Haut-Rhin" +        } +    },  +    { +        "pk": 70,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 50,  +            "number": "69",  +            "label": "Rh\u00f4ne" +        } +    },  +    { +        "pk": 71,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 43,  +            "number": "70",  +            "label": "Haute-Sa\u00f4ne" +        } +    },  +    { +        "pk": 72,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 39,  +            "number": "71",  +            "label": "Sa\u00f4ne-et-Loire" +        } +    },  +    { +        "pk": 73,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 44,  +            "number": "72",  +            "label": "Sarthe" +        } +    },  +    { +        "pk": 74,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 50,  +            "number": "73",  +            "label": "Savoie" +        } +    },  +    { +        "pk": 75,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 50,  +            "number": "74",  +            "label": "Haute-Savoie" +        } +    },  +    { +        "pk": 76,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 33,  +            "number": "75",  +            "label": "Paris" +        } +    },  +    { +        "pk": 77,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 36,  +            "number": "76",  +            "label": "Seine-Maritime" +        } +    },  +    { +        "pk": 78,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 33,  +            "number": "77",  +            "label": "Seine-et-Marne" +        } +    },  +    { +        "pk": 79,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 33,  +            "number": "78",  +            "label": "Yvelines" +        } +    },  +    { +        "pk": 80,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 46,  +            "number": "79",  +            "label": "Deux-S\u00e8vres" +        } +    },  +    { +        "pk": 81,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 35,  +            "number": "80",  +            "label": "Somme" +        } +    },  +    { +        "pk": 82,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 48,  +            "number": "81",  +            "label": "Tarn" +        } +    },  +    { +        "pk": 83,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 48,  +            "number": "82",  +            "label": "Tarn-et-Garonne" +        } +    },  +    { +        "pk": 84,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 53,  +            "number": "83",  +            "label": "Var" +        } +    },  +    { +        "pk": 85,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 53,  +            "number": "84",  +            "label": "Vaucluse" +        } +    },  +    { +        "pk": 86,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 44,  +            "number": "85",  +            "label": "Vend\u00e9e" +        } +    },  +    { +        "pk": 87,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 46,  +            "number": "86",  +            "label": "Vienne" +        } +    },  +    { +        "pk": 88,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 49,  +            "number": "87",  +            "label": "Haute-Vienne" +        } +    },  +    { +        "pk": 89,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 41,  +            "number": "88",  +            "label": "Vosges" +        } +    },  +    { +        "pk": 90,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 39,  +            "number": "89",  +            "label": "Yonne" +        } +    },  +    { +        "pk": 91,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 43,  +            "number": "90",  +            "label": "Territoire de Belfort" +        } +    },  +    { +        "pk": 92,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 33,  +            "number": "91",  +            "label": "Essonne" +        } +    },  +    { +        "pk": 93,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 33,  +            "number": "92",  +            "label": "Hauts-de-Seine" +        } +    },  +    { +        "pk": 94,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 33,  +            "number": "93",  +            "label": "Seine-Saint-Denis" +        } +    },  +    { +        "pk": 95,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 33,  +            "number": "94",  +            "label": "Val-de-Marne" +        } +    },  +    { +        "pk": 96,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 33,  +            "number": "95",  +            "label": "Val-d'Oise" +        } +    },  +    { +        "pk": 97,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 28,  +            "number": "971",  +            "label": "Guadeloupe" +        } +    },  +    { +        "pk": 98,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 29,  +            "number": "972",  +            "label": "Martinique" +        } +    },  +    { +        "pk": 99,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 30,  +            "number": "973",  +            "label": "Guyane" +        } +    },  +    { +        "pk": 100,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 31,  +            "number": "974",  +            "label": "La R\u00e9union" +        } +    },  +    { +        "pk": 101,  +        "model": "ishtar_common.department",  +        "fields": { +            "state": 32,  +            "number": "976",  +            "label": "Mayotte" +        } +    } +]
\ No newline at end of file diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py index 88a3306f4..e6f21ae5b 100644 --- a/ishtar_common/forms.py +++ b/ishtar_common/forms.py @@ -1,6 +1,6 @@  #!/usr/bin/env python  # -*- coding: utf-8 -*- -# Copyright (C) 2010-2014 Étienne Loks  <etienne.loks_AT_peacefrogsDOTnet> +# Copyright (C) 2010-2015 É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 diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py index 091d31c1c..664dfb775 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 @@ -193,6 +201,13 @@ class OrganizationFormSelection(forms.Form):                                       OrganizationSelect, models.Organization),           validators=[models.valid_id(models.Organization)]) +class BaseOrganizationForm(forms.ModelForm): +    form_prefix = "orga" +    class Meta: +        model = models.Organization +        fields = ['name', 'organization_type', 'address', 'address_complement', +                  'town', 'postal_code',] +  class PersonSelect(TableSelect):      name = forms.CharField(label=_(u"Name"), max_length=30)      surname = forms.CharField(label=_(u"Surname"), max_length=20) @@ -244,6 +259,39 @@ class SimplePersonForm(NewItemForm):                      associated_model=models.Organization, new=True),          validators=[models.valid_id(models.Organization)], required=False) +class BasePersonForm(forms.ModelForm): +    class Meta: +        model = models.Person +        fields = ['title', 'name', 'surname', 'address', 'address_complement', +                  'town', 'postal_code'] + +class BaseOrganizationPersonForm(forms.ModelForm): +    class Meta: +        model = models.Person +        fields = ['attached_to', 'title', 'name', 'surname',] +        widgets = {'attached_to':widgets.JQueryPersonOrganization( +                reverse_lazy('autocomplete-organization'), +                reverse_lazy('organization_create'), +                model=models.Organization, +                attrs={'hidden':True}, +                new=True), +        } + +    def __init__(self, *args, **kwargs): +        super(BaseOrganizationPersonForm, self).__init__(*args, **kwargs) + +    def save(self, *args, **kwargs): +        person = super(BaseOrganizationPersonForm, self).save(*args, **kwargs) +        instance = person.attached_to +        form = BaseOrganizationForm(self.data, instance=instance, +                                    prefix=BaseOrganizationForm.form_prefix) +        if form.is_valid(): +            orga = form.save() +            if not person.attached_to: +                person.attached_to = orga +                person.save() +        return person +  class PersonForm(SimplePersonForm):      person_types = forms.MultipleChoiceField(label=_("Person type"),                               choices=[], widget=forms.CheckboxSelectMultiple) diff --git a/ishtar_common/ishtar_menu.py b/ishtar_common/ishtar_menu.py index fdfe60448..a26234265 100644 --- a/ishtar_common/ishtar_menu.py +++ b/ishtar_common/ishtar_menu.py @@ -39,6 +39,9 @@ MENU_SECTIONS = [              childs=[                  SectionItem('person', _(u"Person"),                  childs=[ +                    MenuItem('person_search', _(u"Search"), +                        model=models.Person, +                        access_controls=['add_person', 'add_own_person']),                      MenuItem('person_creation', _(u"Creation"),                          model=models.Person,                          access_controls=['add_person', 'add_own_person']), @@ -54,6 +57,10 @@ MENU_SECTIONS = [                      ]),                  SectionItem('organization', _(u"Organization"),                  childs=[ +                    MenuItem('organization_search', _(u"Search"), +                        model=models.Organization, +                        access_controls=['add_organization', +                                         'add_own_organization']),                      MenuItem('organization_creation', _(u"Creation"),                          model=models.Organization,                          access_controls=['add_organization', diff --git a/ishtar_common/locale/fr/LC_MESSAGES/django.po b/ishtar_common/locale/fr/LC_MESSAGES/django.po index 224d480f2..48c5cd2b1 100644 --- a/ishtar_common/locale/fr/LC_MESSAGES/django.po +++ b/ishtar_common/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid ""  msgstr ""  "Project-Id-Version: alpha\n"  "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-06-02 01:14+0200\n" +"POT-Creation-Date: 2015-06-02 01:17+0200\n"  "PO-Revision-Date: 2015-01-25\n"  "Last-Translator: Étienne Loks <etienne.loks at peacefrogs net>\n"  "Language-Team: \n" @@ -32,7 +32,7 @@ msgstr "courriel"  msgid "Archaeological file"  msgstr "Dossier" -#: context_processors.py:35 models.py:970 +#: context_processors.py:35 models.py:983  msgid "Operation"  msgstr "Opération" @@ -44,49 +44,49 @@ msgstr "Unité d'Enregistrement"  msgid "Find"  msgstr "Mobilier" -#: data_importer.py:123 +#: data_importer.py:134  #, python-format  msgid "\"%(value)s\" is too long. The max length is %(length)d characters."  msgstr ""  "\"%(value)s\" est trop long. La longueur maximum est de  %(length)d "  "caractères." -#: data_importer.py:137 +#: data_importer.py:148  #, python-format  msgid "\"%(value)s\" not equal to yes or no"  msgstr "\"%(value)s\" diffère de oui ou non" -#: data_importer.py:148 +#: data_importer.py:159  #, python-format  msgid "\"%(value)s\" is not a float"  msgstr "\"%(value)s\" n'est pas un flottant" -#: data_importer.py:160 data_importer.py:172 data_importer.py:346 +#: data_importer.py:171 data_importer.py:183 data_importer.py:372  #, python-format  msgid "\"%(value)s\" is not a valid date"  msgstr "\"%(value)s\" n'est pas une date valide" -#: data_importer.py:183 +#: data_importer.py:194  #, python-format  msgid "\"%(value)s\" is not an integer"  msgstr "\"%(value)s\" n'est pas un entier" -#: data_importer.py:223 data_importer.py:380 +#: data_importer.py:236 data_importer.py:408  #, python-format  msgid "Choice for \"%s\" is not available. Which one is relevant?\n"  msgstr "Le choix pour \"%s\" n'est pas disponible. Lequel est pertinent ?\n" -#: data_importer.py:230 +#: data_importer.py:243  #, python-format  msgid "%d. None of the above - create new"  msgstr "%d. Aucun de ceux-là - créer un nouveau" -#: data_importer.py:233 +#: data_importer.py:246  #, python-format  msgid "%d. None of the above - skip"  msgstr "%d. Aucun de ceux-là - passer" -#: data_importer.py:430 +#: data_importer.py:474  msgid ""  "The given file is not correct. Check the file format. If you use a CSV file: "  "check that column separator and encoding are similar to the ones used by the " @@ -96,41 +96,41 @@ msgstr ""  "utilisez un fichier CSV : vérifiez que le séparateur de colonne et "  "l'encodage sont similaires à ceux du fichier de référence." -#: data_importer.py:434 +#: data_importer.py:478  #, python-format  msgid "Too many cols (%(user_col)d) when maximum is %(ref_col)d"  msgstr "Trop de colonnes (%(user_col)d). Le maximum est %(ref_col)d" -#: data_importer.py:436 +#: data_importer.py:480  msgid "No data provided"  msgstr "Aucune donnée fournie" -#: data_importer.py:437 +#: data_importer.py:481  msgid "Value is required"  msgstr "Valeur requise" -#: data_importer.py:438 +#: data_importer.py:482  #, python-format  msgid "At least %d columns must be filled"  msgstr "Au moins %d colonnes doivent être remplies" -#: data_importer.py:439 +#: data_importer.py:483  msgid "The regexp doesn't match."  msgstr "L'expression régulière ne fonctionne pas" -#: data_importer.py:896 +#: data_importer.py:963  msgid "line"  msgstr "ligne" -#: data_importer.py:896 +#: data_importer.py:963  msgid "col"  msgstr "colonne" -#: data_importer.py:896 +#: data_importer.py:963  msgid "error"  msgstr "erreur" -#: data_importer.py:909 +#: data_importer.py:976  #, python-format  msgid "\"%(value)s\" not in %(values)s"  msgstr "\"%(value)s\" n'est pas dans %(values)s" @@ -139,7 +139,7 @@ msgstr "\"%(value)s\" n'est pas dans %(values)s"  msgid "Enter a valid name consisting of letters, spaces and hyphens."  msgstr "Entrez un nom correct composé de lettres, espaces et tirets." -#: forms.py:76 forms_common.py:331 +#: forms.py:76 forms_common.py:379  msgid "Confirm"  msgstr "Confirmation" @@ -167,8 +167,9 @@ msgstr "Ajouter un nouvel élément"  msgid "Template"  msgstr "Patron" -#: forms_common.py:43 forms_common.py:153 forms_common.py:234 -#: forms_common.py:239 models.py:901 models.py:1777 +#: forms_common.py:43 forms_common.py:57 forms_common.py:161 +#: forms_common.py:249 forms_common.py:254 models.py:914 models.py:1805 +#: templates/blocks/JQueryAdvancedTown.html:19  #: templates/ishtar/sheet_organization.html:17  #: templates/ishtar/sheet_person.html:20 templates/ishtar/sheet_person.html:30  msgid "Town" @@ -192,70 +193,70 @@ msgstr ""  "<p class='example'>Par exemple tapez « saint denis 93 » pour obtenir la "  "commune Saint-Denis dans le département français de Seine-Saint-Denis.</p>" -#: forms_common.py:57 forms_common.py:519 ishtar_menu.py:40 models.py:1553 -#: models.py:1639 models.py:1679 templates/ishtar/sheet_person.html:6 +#: forms_common.py:65 forms_common.py:567 ishtar_menu.py:40 models.py:1566 +#: models.py:1667 models.py:1707 templates/ishtar/sheet_person.html:6  msgid "Person"  msgstr "Individu" -#: forms_common.py:141 forms_common.py:201 ishtar_menu.py:55 models.py:1491 +#: forms_common.py:149 forms_common.py:216 ishtar_menu.py:58 models.py:1504  #: templates/ishtar/sheet_organization.html:6  msgid "Organization"  msgstr "Organisation" -#: forms_common.py:143 forms_common.py:179 forms_common.py:197 -#: forms_common.py:226 models.py:850 models.py:1005 models.py:1163 -#: models.py:1486 models.py:1543 models.py:1763 +#: forms_common.py:151 forms_common.py:187 forms_common.py:212 +#: forms_common.py:241 models.py:850 models.py:1018 models.py:1176 +#: models.py:1499 models.py:1556 models.py:1791  #: templates/ishtar/sheet_organization.html:12  #: templates/ishtar/sheet_organization.html:25  #: templates/ishtar/sheet_person.html:12 templates/ishtar/sheet_person.html:26  msgid "Name"  msgstr "Nom" -#: forms_common.py:145 models.py:965 models.py:1244 +#: forms_common.py:153 models.py:978 models.py:1257  msgid "Organization type"  msgstr "Type d'organisation" -#: forms_common.py:147 forms_common.py:228 models.py:896 +#: forms_common.py:155 forms_common.py:243 models.py:909  #: templates/ishtar/sheet_organization.html:14  #: templates/ishtar/sheet_person.html:17 templates/ishtar/sheet_person.html:27  msgid "Address"  msgstr "Adresse" -#: forms_common.py:149 forms_common.py:230 models.py:897 +#: forms_common.py:157 forms_common.py:245 models.py:910  #: templates/ishtar/sheet_organization.html:15  #: templates/ishtar/sheet_person.html:18 templates/ishtar/sheet_person.html:28  msgid "Address complement"  msgstr "Complément d'adresse" -#: forms_common.py:151 forms_common.py:232 models.py:899 +#: forms_common.py:159 forms_common.py:247 models.py:912  #: templates/ishtar/sheet_organization.html:16  #: templates/ishtar/sheet_person.html:19 templates/ishtar/sheet_person.html:29  msgid "Postal code"  msgstr "Code postal" -#: forms_common.py:154 forms_common.py:235 models.py:902 +#: forms_common.py:162 forms_common.py:250 models.py:915  msgid "Country"  msgstr "Pays" -#: forms_common.py:156 forms_common.py:199 forms_common.py:237 -#: forms_common.py:289 models.py:907 templates/ishtar/sheet_person.html:15 +#: forms_common.py:164 forms_common.py:214 forms_common.py:252 +#: forms_common.py:337 models.py:920 templates/ishtar/sheet_person.html:15  msgid "Email"  msgstr "Courriel" -#: forms_common.py:157 forms_common.py:238 models.py:904 +#: forms_common.py:165 forms_common.py:253 models.py:917  #: templates/ishtar/sheet_organization.html:18  #: templates/ishtar/sheet_person.html:21 templates/ishtar/sheet_person.html:31  msgid "Phone"  msgstr "Téléphone" -#: forms_common.py:158 models.py:905 +#: forms_common.py:166 models.py:918  #: templates/ishtar/sheet_organization.html:19  #: templates/ishtar/sheet_person.html:22 templates/ishtar/sheet_person.html:32  msgid "Mobile phone"  msgstr "Téléphone portable" -#: forms_common.py:180 forms_common.py:200 models.py:1259 models.py:1488 -#: models.py:1715 templates/sheet_ope.html:85 templates/sheet_ope.html.py:105 +#: forms_common.py:188 forms_common.py:215 models.py:1272 models.py:1501 +#: models.py:1743 templates/sheet_ope.html:85 templates/sheet_ope.html.py:105  #: templates/sheet_ope.html:126 templates/ishtar/import_list.html:13  #: templates/ishtar/sheet_organization.html:27  #: templates/ishtar/sheet_person.html:42 templates/ishtar/sheet_person.html:95 @@ -263,121 +264,121 @@ msgstr "Téléphone portable"  msgid "Type"  msgstr "Type" -#: forms_common.py:188 +#: forms_common.py:196 views.py:106  msgid "Organization search"  msgstr "Recherche d'organisations" -#: forms_common.py:198 forms_common.py:224 models.py:1541 +#: forms_common.py:213 forms_common.py:239 models.py:1554  #: templates/ishtar/sheet_organization.html:26  #: templates/ishtar/sheet_person.html:13  msgid "Surname"  msgstr "Prénom" -#: forms_common.py:212 +#: forms_common.py:227 views.py:80  msgid "Person search"  msgstr "Recherche d'individus" -#: forms_common.py:221 +#: forms_common.py:236  msgid "Identity"  msgstr "Identité" -#: forms_common.py:223 forms_common.py:476 models.py:1539 models.py:1712 +#: forms_common.py:238 forms_common.py:524 models.py:1552 models.py:1740  #: templates/sheet_ope.html:104 templates/ishtar/sheet_person.html:94  #: templates/ishtar/blocks/window_tables/documents.html:5  msgid "Title"  msgstr "Titre" -#: forms_common.py:241 +#: forms_common.py:256  msgid "Current organization"  msgstr "Organisation actuelle" -#: forms_common.py:248 forms_common.py:271 forms_common.py:274 models.py:1524 +#: forms_common.py:296 forms_common.py:319 forms_common.py:322 models.py:1537  msgid "Person type"  msgstr "Type d'individu" -#: forms_common.py:284 forms_common.py:288 +#: forms_common.py:332 forms_common.py:336  msgid "Account"  msgstr "Compte" -#: forms_common.py:291 wizards.py:938 +#: forms_common.py:339 wizards.py:948  msgid "New password"  msgstr "Nouveau mot de passe" -#: forms_common.py:295 +#: forms_common.py:343  msgid "New password (confirmation)"  msgstr "Nouveau mot de passe (confirmation)" -#: forms_common.py:313 +#: forms_common.py:361  msgid "Your password and confirmation password do not match."  msgstr "La vérification du mot de passe a échoué." -#: forms_common.py:318 +#: forms_common.py:366  msgid "You must provide a correct password."  msgstr "Vous devez fournir un mot de passe correct." -#: forms_common.py:326 +#: forms_common.py:374  msgid "This username already exists."  msgstr "Ce nom d'utilisateur existe déjà." -#: forms_common.py:332 +#: forms_common.py:380  msgid "Send the new password by email?"  msgstr "Envoyer le nouveau mot de passe par courriel ?" -#: forms_common.py:340 forms_common.py:352 models.py:1778 +#: forms_common.py:388 forms_common.py:400 models.py:1806  #: templates/ishtar/sheet_person.html:72  msgid "Towns"  msgstr "Communes" -#: forms_common.py:349 +#: forms_common.py:397  msgid "There are identical towns."  msgstr "Il y a des communes identiques." -#: forms_common.py:428 +#: forms_common.py:476  msgid "Only one choice can be checked."  msgstr "Seul un choix peut être coché." -#: forms_common.py:474 +#: forms_common.py:522  msgid "Documentation informations"  msgstr "Information sur le document" -#: forms_common.py:478 forms_common.py:497 models.py:1245 models.py:1698 +#: forms_common.py:526 forms_common.py:545 models.py:1258 models.py:1726  msgid "Source type"  msgstr "Type de source" -#: forms_common.py:480 models.py:1725 +#: forms_common.py:528 models.py:1753  msgid "Numerical ressource (web address)"  msgstr "Ressource numérique (adresse web)" -#: forms_common.py:481 models.py:1727 +#: forms_common.py:529 models.py:1755  msgid "Receipt date"  msgstr "Date de réception" -#: forms_common.py:483 models.py:1355 models.py:1729 +#: forms_common.py:531 models.py:1368 models.py:1757  msgid "Creation date"  msgstr "Date de création" -#: forms_common.py:494 forms_common.py:513 forms_common.py:544 models.py:1684 +#: forms_common.py:542 forms_common.py:561 forms_common.py:592 models.py:1712  #: templates/ishtar/wizard/wizard_person_deletion.html:124  msgid "Author"  msgstr "Auteur" -#: forms_common.py:506 +#: forms_common.py:554  msgid "Would you like to delete this documentation?"  msgstr "Voulez-vous supprimer ce document ?" -#: forms_common.py:520 models.py:1246 models.py:1675 models.py:1681 +#: forms_common.py:568 models.py:1259 models.py:1703 models.py:1709  msgid "Author type"  msgstr "Type d'auteur" -#: forms_common.py:538 +#: forms_common.py:586  msgid "Author selection"  msgstr "Sélection d'auteur" -#: forms_common.py:550 +#: forms_common.py:598  msgid "There are identical authors."  msgstr "Il y a des auteurs identiques." -#: forms_common.py:554 models.py:1685 models.py:1722 +#: forms_common.py:602 models.py:1713 models.py:1750  #: templates/sheet_ope.html:106  #: templates/ishtar/blocks/window_tables/documents.html:7  msgid "Authors" @@ -387,11 +388,11 @@ msgstr "Auteurs"  msgid "Administration"  msgstr "Administration" -#: ishtar_menu.py:30 views.py:122 +#: ishtar_menu.py:30 views.py:132  msgid "Account management"  msgstr "Gestion des comptes" -#: ishtar_menu.py:33 models.py:641 views.py:864 +#: ishtar_menu.py:33 models.py:641 views.py:930  msgid "Global variables"  msgstr "Variables globales" @@ -399,35 +400,39 @@ msgstr "Variables globales"  msgid "Directory"  msgstr "Annuaire" -#: ishtar_menu.py:42 ishtar_menu.py:57 templates/ishtar/import_list.html:15 +#: ishtar_menu.py:42 ishtar_menu.py:60 templates/blocks/JQueryJqGrid.html:4 +msgid "Search" +msgstr "Recherche" + +#: ishtar_menu.py:45 ishtar_menu.py:64 templates/ishtar/import_list.html:15  msgid "Creation"  msgstr "Ajout" -#: ishtar_menu.py:45 ishtar_menu.py:61 +#: ishtar_menu.py:48 ishtar_menu.py:68  msgid "Modification"  msgstr "Modification" -#: ishtar_menu.py:48 ishtar_menu.py:65 templates/ishtar/merge.html:5 +#: ishtar_menu.py:51 ishtar_menu.py:72 templates/ishtar/merge.html:5  msgid "Merge"  msgstr "Fusion" -#: ishtar_menu.py:51 ishtar_menu.py:68 models.py:1389 widgets.py:107 +#: ishtar_menu.py:54 ishtar_menu.py:75 models.py:1402 widgets.py:108  msgid "Delete"  msgstr "Suppression" -#: ishtar_menu.py:75 models.py:1363 +#: ishtar_menu.py:82 models.py:1376  msgid "Imports"  msgstr "Imports" -#: ishtar_menu.py:77 views.py:871 +#: ishtar_menu.py:84 views.py:937  msgid "New import"  msgstr "Nouvel import" -#: ishtar_menu.py:80 views.py:884 +#: ishtar_menu.py:87 views.py:950  msgid "Current imports"  msgstr "Imports en cours" -#: ishtar_menu.py:83 +#: ishtar_menu.py:90  msgid "Old imports"  msgstr "Anciens imports" @@ -443,7 +448,7 @@ msgstr "Un élément sélectionné n'est pas valide."  msgid "This item already exist."  msgstr "Cet élément existe déjà." -#: models.py:239 models.py:884 +#: models.py:239 models.py:884 models.py:895  msgid "Label"  msgstr "Libellé" @@ -451,7 +456,7 @@ msgstr "Libellé"  msgid "Textual ID"  msgstr "Identifiant textuel" -#: models.py:242 models.py:1736 +#: models.py:242 models.py:1764  msgid "Comment"  msgstr "Commentaire" @@ -459,7 +464,7 @@ msgstr "Commentaire"  msgid "Available"  msgstr "Disponible" -#: models.py:415 models.py:1207 +#: models.py:415 models.py:1220  msgid "Key"  msgstr "Clé" @@ -483,7 +488,7 @@ msgstr "Nom de la variable"  msgid "Description of the variable"  msgstr "Description de la variable" -#: models.py:638 models.py:1208 +#: models.py:638 models.py:1221  msgid "Value"  msgstr "Valeur" @@ -495,7 +500,7 @@ msgstr "Variables globales"  msgid "Total"  msgstr "Total" -#: models.py:762 models.py:885 +#: models.py:762 models.py:885 models.py:896  #: templates/ishtar/dashboards/dashboard_main_detail.html:135  #: templates/ishtar/dashboards/dashboard_main_detail_users.html:26  msgid "Number" @@ -517,439 +522,439 @@ msgstr "Patron de document"  msgid "Document templates"  msgstr "Patrons de documents" -#: models.py:888 +#: models.py:888 models.py:897 models.py:1366 +msgid "State" +msgstr "État" + +#: models.py:901 templates/blocks/JQueryAdvancedTown.html:12  msgid "Department"  msgstr "Département" -#: models.py:889 +#: models.py:902  msgid "Departments"  msgstr "Départements" -#: models.py:914 +#: models.py:927  msgid "Merge key"  msgstr "Clé de fusion" -#: models.py:966 +#: models.py:979  msgid "Organization types"  msgstr "Types d'organisation" -#: models.py:971 +#: models.py:984  msgid "Parcels"  msgstr "Parcelles" -#: models.py:973 +#: models.py:986  msgid "Operation source"  msgstr "Documentation de l'opération" -#: models.py:976 views.py:709 views.py:765 +#: models.py:989 views.py:761 views.py:817  msgid "Archaeological files"  msgstr "Dossiers archéologiques" -#: models.py:980 views.py:712 views.py:774 +#: models.py:993 views.py:764 views.py:826  msgid "Context records"  msgstr "Unité d'Enregistrement" -#: models.py:1007 models.py:1164 models.py:1735 templates/sheet_ope.html:128 +#: models.py:1020 models.py:1177 models.py:1763 templates/sheet_ope.html:128  msgid "Description"  msgstr "Description" -#: models.py:1009 templates/ishtar/dashboards/dashboard_main.html:26 +#: models.py:1022 templates/ishtar/dashboards/dashboard_main.html:26  msgid "Users"  msgstr "Utilisateurs" -#: models.py:1011 +#: models.py:1024  msgid "Associated model"  msgstr "Modèle associé" -#: models.py:1013 +#: models.py:1026  msgid "Is template"  msgstr "Est un patron" -#: models.py:1015 +#: models.py:1028  msgid "Importer - Type"  msgstr "Importeur - Type" -#: models.py:1016 +#: models.py:1029  msgid "Importer - Types"  msgstr "Importeur - Types" -#: models.py:1085 +#: models.py:1098  msgid "Importer - Default"  msgstr "Importeur - Par défaut" -#: models.py:1086 +#: models.py:1099  msgid "Importer - Defaults"  msgstr "Importeur - Par défaut" -#: models.py:1112 +#: models.py:1125  msgid "Importer - Default value"  msgstr "Importeur - Valeur par défaut" -#: models.py:1113 +#: models.py:1126  msgid "Importer - Default values"  msgstr "Importeur - Valeurs par défaut" -#: models.py:1144 +#: models.py:1157  msgid "Column number"  msgstr "Numéro de colonne" -#: models.py:1146 +#: models.py:1159  msgid "Required"  msgstr "Requis" -#: models.py:1148 +#: models.py:1161  msgid "Importer - Column"  msgstr "Importeur - Colonne" -#: models.py:1149 +#: models.py:1162  msgid "Importer - Columns"  msgstr "Importeur - Colonnes" -#: models.py:1156 +#: models.py:1169  msgid "Field name"  msgstr "Nom du champ" -#: models.py:1159 +#: models.py:1172  msgid "Importer - Duplicate field"  msgstr "Importeur - Champ dupliqué" -#: models.py:1160 +#: models.py:1173  msgid "Importer - Duplicate fields"  msgstr "Importeur - Champs dupliqués" -#: models.py:1166 +#: models.py:1179  msgid "Regular expression"  msgstr "Expression régulière" -#: models.py:1168 +#: models.py:1181  msgid "Importer - Regular expression"  msgstr "Importeur - Expression régulière" -#: models.py:1169 +#: models.py:1182  msgid "Importer - Regular expressions"  msgstr "Importeur - Expressions régulières" -#: models.py:1183 +#: models.py:1196  msgid "Importer - Target"  msgstr "Importeur - Cible" -#: models.py:1184 +#: models.py:1197  msgid "Importer - Targets"  msgstr "Importeur - Cibles" -#: models.py:1209 +#: models.py:1222  msgid "Is set"  msgstr "Est défini" -#: models.py:1213 +#: models.py:1226  msgid "Importer - Target key"  msgstr "Importeur - Rapprochement" -#: models.py:1214 +#: models.py:1227  msgid "Importer - Targets keys"  msgstr "Importeur - Rapprochements" -#: models.py:1247 models.py:1708 models.py:1718 +#: models.py:1260 models.py:1736 models.py:1746  msgid "Format"  msgstr "Format" -#: models.py:1248 +#: models.py:1261  msgid "Operation type"  msgstr "Type d'opération" -#: models.py:1249 +#: models.py:1262  msgid "Period"  msgstr "Périodes" -#: models.py:1255 +#: models.py:1268  msgid "Integer"  msgstr "Entier" -#: models.py:1256 +#: models.py:1269  msgid "Float"  msgstr "Nombre à virgule" -#: models.py:1257 +#: models.py:1270  msgid "String"  msgstr "Chaine de caractères" -#: models.py:1258 templates/sheet_ope.html:86 +#: models.py:1271 templates/sheet_ope.html:86  msgid "Date"  msgstr "Date" -#: models.py:1260 templates/sheet_ope.html:61 templates/sheet_ope.html.py:83 +#: models.py:1273 templates/sheet_ope.html:61 templates/sheet_ope.html.py:83  #: templates/ishtar/sheet_person.html:39 templates/ishtar/sheet_person.html:68  #: templates/ishtar/sheet_person.html:93  #: templates/ishtar/dashboards/dashboard_main_detail.html:120  msgid "Year"  msgstr "Année" -#: models.py:1273 +#: models.py:1286  msgid "4 digit year. e.g.: \"2015\""  msgstr "Année à 4 chiffres. Exemple : « 2015 »" -#: models.py:1274 +#: models.py:1287  msgid "4 digit year/month/day. e.g.: \"2015/02/04\""  msgstr "Année à 4 chiffres/mois/jour. Exemple : « 2015/02/04 »" -#: models.py:1275 +#: models.py:1288  msgid "Day/month/4 digit year. e.g.: \"04/02/2015\""  msgstr "Jour/mois/année à 4 chiffres. Exemple : « 04/02/2015 »" -#: models.py:1284 +#: models.py:1297  msgid "Options"  msgstr "Options" -#: models.py:1286 +#: models.py:1299  msgid "Split character(s)"  msgstr "Caractère de séparation" -#: models.py:1289 +#: models.py:1302  msgid "Importer - Formater type"  msgstr "Importeur - Type de mise en forme" -#: models.py:1290 +#: models.py:1303  msgid "Importer - Formater types"  msgstr "Importeur - Types de mise en forme" -#: models.py:1329 templates/ishtar/dashboards/dashboard_main_detail.html:61 +#: models.py:1342 templates/ishtar/dashboards/dashboard_main_detail.html:61  msgid "Created"  msgstr "Créé" -#: models.py:1330 +#: models.py:1343  msgid "Analyse in progress"  msgstr "Analyse en cours" -#: models.py:1331 +#: models.py:1344  msgid "Analysed"  msgstr "Analysé" -#: models.py:1332 +#: models.py:1345  msgid "Import pending"  msgstr "Import en attente" -#: models.py:1333 +#: models.py:1346  msgid "Import in progress"  msgstr "Import en cours" -#: models.py:1334 +#: models.py:1347  msgid "Finished with errors"  msgstr "Finis avec des erreurs" -#: models.py:1335 +#: models.py:1348  msgid "Finished"  msgstr "Terminé" -#: models.py:1336 +#: models.py:1349  msgid "Archived"  msgstr "Archivé" -#: models.py:1344 +#: models.py:1357  msgid "Imported file"  msgstr "Fichier importé" -#: models.py:1346 +#: models.py:1359  msgid "Skip lines"  msgstr "Nombre de lignes d'entête" -#: models.py:1347 templates/ishtar/import_list.html:47 +#: models.py:1360 templates/ishtar/import_list.html:47  msgid "Error file"  msgstr "Fichier erreur" -#: models.py:1350 +#: models.py:1363  msgid "Result file"  msgstr "Fichier résultant" -#: models.py:1353 -msgid "State" -msgstr "État" - -#: models.py:1357 +#: models.py:1370  msgid "End date"  msgstr "Date de fin" -#: models.py:1359 +#: models.py:1372  msgid "Seconds remaining"  msgstr "Secondes restantes" -#: models.py:1362 +#: models.py:1375  msgid "Import"  msgstr "Import" -#: models.py:1379 +#: models.py:1392  msgid "Analyse"  msgstr "Analyser" -#: models.py:1381 models.py:1384 +#: models.py:1394 models.py:1397  msgid "Re-analyse"  msgstr "Analyser de nouveau " -#: models.py:1382 +#: models.py:1395  msgid "Launch import"  msgstr "Lancer l'import" -#: models.py:1385 +#: models.py:1398  msgid "Re-import" -msgstr "Re-import" +msgstr "re-import" -#: models.py:1386 +#: models.py:1399  msgid "Archive"  msgstr "Archiver" -#: models.py:1388 +#: models.py:1401  msgid "Unarchive"  msgstr "Dé-archiver" -#: models.py:1492 +#: models.py:1505  msgid "Organizations"  msgstr "Organisations" -#: models.py:1494 +#: models.py:1507  msgid "Can view all Organization"  msgstr "Peut voir toutes les Organisations" -#: models.py:1495 +#: models.py:1508  msgid "Can view own Organization"  msgstr "Peut voir sa propre Organisation" -#: models.py:1496 +#: models.py:1509  msgid "Can add own Organization"  msgstr "Peut ajouter sa propre Organisation" -#: models.py:1497 +#: models.py:1510  msgid "Can change own Organization"  msgstr "Peut changer sa propre Organisation" -#: models.py:1498 +#: models.py:1511  msgid "Can delete own Organization"  msgstr "Peut supprimer sa propre Organisation" -#: models.py:1521 +#: models.py:1534  msgid "Groups"  msgstr "Groupes" -#: models.py:1525 +#: models.py:1538  msgid "Person types"  msgstr "Types d'individu" -#: models.py:1531 +#: models.py:1544  msgid "Mr"  msgstr "M." -#: models.py:1532 +#: models.py:1545  msgid "Miss"  msgstr "Mlle" -#: models.py:1533 +#: models.py:1546  msgid "Mr and Miss"  msgstr "M. et Mme" -#: models.py:1534 +#: models.py:1547  msgid "Mrs"  msgstr "Mme" -#: models.py:1535 +#: models.py:1548  msgid "Doctor"  msgstr "Dr." -#: models.py:1545 +#: models.py:1558  msgid "Raw name"  msgstr "Nom brut" -#: models.py:1547 models.py:1584 +#: models.py:1560 models.py:1600  msgid "Types"  msgstr "Types" -#: models.py:1550 +#: models.py:1563  msgid "Is attached to"  msgstr "Est rattaché à" -#: models.py:1554 +#: models.py:1567  msgid "Persons"  msgstr "Individus" -#: models.py:1556 +#: models.py:1569  msgid "Can view all Person"  msgstr "Peut voir toutes les Personnes" -#: models.py:1557 +#: models.py:1570  msgid "Can view own Person"  msgstr "Peut voir sa propre Personne" -#: models.py:1558 +#: models.py:1571  msgid "Can add own Person"  msgstr "Peut ajouter sa propre Personne" -#: models.py:1559 +#: models.py:1572  msgid "Can change own Person"  msgstr "Peut changer sa propre Personne" -#: models.py:1560 +#: models.py:1573  msgid "Can delete own Person"  msgstr "Peut supprimer sa propre Personne" -#: models.py:1643 +#: models.py:1671  msgid "Ishtar user"  msgstr "Utilisateur d'Ishtar" -#: models.py:1644 +#: models.py:1672  msgid "Ishtar users"  msgstr "Utilisateurs d'Ishtar" -#: models.py:1676 +#: models.py:1704  msgid "Author types"  msgstr "Types d'auteur" -#: models.py:1699 +#: models.py:1727  msgid "Source types"  msgstr "Types de source" -#: models.py:1703 +#: models.py:1731  msgid "Support type"  msgstr "Type de support" -#: models.py:1704 +#: models.py:1732  msgid "Support types"  msgstr "Types de support" -#: models.py:1709 +#: models.py:1737  msgid "Formats"  msgstr "Formats" -#: models.py:1713 +#: models.py:1741  msgid "External ID"  msgstr "Identifiant extern" -#: models.py:1716 +#: models.py:1744  msgid "Support"  msgstr "Support" -#: models.py:1720 +#: models.py:1748  msgid "Scale"  msgstr "Échelle" -#: models.py:1730 +#: models.py:1758  msgid "Item number"  msgstr "Numéro d'élément" -#: models.py:1731 templates/ishtar/sheet_person.html:40 +#: models.py:1759 templates/ishtar/sheet_person.html:40  #: templates/ishtar/sheet_person.html:67  msgid "Ref."  msgstr "Réf." -#: models.py:1733 +#: models.py:1761  msgid "Internal reference"  msgstr "Référence interne" -#: models.py:1737 +#: models.py:1765  msgid "Additional information"  msgstr "Informations supplémentaires" -#: models.py:1764 +#: models.py:1792  msgid "Surface (m²)"  msgstr "Surface (m²)" -#: models.py:1765 templates/sheet_ope.html:46 templates/sheet_ope.html.py:107 +#: models.py:1793 templates/sheet_ope.html:46 templates/sheet_ope.html.py:107  msgid "Localisation"  msgstr "Localisation" @@ -957,90 +962,98 @@ msgstr "Localisation"  msgid " (...)"  msgstr " (...)" -#: views.py:82 +#: views.py:87  msgid "New person"  msgstr "Nouvelle personne" -#: views.py:90 +#: views.py:95  msgid "Person modification"  msgstr "Modification d'une personne" -#: views.py:96 +#: views.py:101  msgid "Person deletion"  msgstr "Suppression de personne" -#: views.py:102 +#: views.py:112  msgid "New organization"  msgstr "Nouvelle organisation" -#: views.py:109 +#: views.py:119  msgid "Organization modification"  msgstr "Modification d'une organisation" -#: views.py:115 +#: views.py:125  msgid "Organization deletion"  msgstr "Suppression d'une organisation" -#: views.py:212 +#: views.py:261  msgid "True"  msgstr "Oui" -#: views.py:214 +#: views.py:263  msgid "False"  msgstr "Non" -#: views.py:437 templates/base.html:75 +#: views.py:486 templates/base.html:76  #: templates/ishtar/sheet_organization.html:35  #: templates/ishtar/sheet_person.html:57 templates/ishtar/sheet_person.html:83  msgid "Details"  msgstr "Détails" -#: views.py:645 views.py:692 +#: views.py:697 views.py:744  msgid "Operation not permitted."  msgstr "Opération non permise." -#: views.py:647 +#: views.py:699  #, python-format  msgid "New %s"  msgstr "Nouveau %s" -#: views.py:710 views.py:769 +#: views.py:762 views.py:821  msgid "Operations"  msgstr "Opérations" -#: views.py:714 views.py:779 +#: views.py:766 views.py:831  msgid "Finds"  msgstr "Mobilier" -#: views.py:928 templates/ishtar/import_list.html:43 +#: views.py:994 templates/ishtar/import_list.html:43  msgid "Link unmatched items"  msgstr "Associer les éléments non rapprochés" -#: views.py:942 +#: views.py:1008  msgid "Delete import"  msgstr "Suppression de l'import" -#: widgets.py:352 +#: views.py:1056 views.py:1071 +msgid "Corporation manager" +msgstr "Représentant de la personne morale" + +#: widgets.py:222 widgets.py:326 widgets.py:441 +msgid "Search..." +msgstr "Recherche..." + +#: widgets.py:552  msgid "No results"  msgstr "Pas de résultats" -#: widgets.py:353 +#: widgets.py:553  msgid "Loading..."  msgstr "Chargement..." -#: widgets.py:354 +#: widgets.py:554  msgid "Remove"  msgstr "Enlever" -#: wizards.py:227 templates/ishtar/import_delete.html:14 +#: wizards.py:230 templates/ishtar/import_delete.html:14  msgid "Yes"  msgstr "Oui" -#: wizards.py:229 +#: wizards.py:232  msgid "No"  msgstr "Non" -#: wizards.py:992 +#: wizards.py:1002  #, python-format  msgid "[%(app_name)s] Account creation/modification"  msgstr "[%(app_name)s] Ajout - modification du compte" @@ -1079,38 +1092,38 @@ msgstr "Merci pour l'intérêt que vous portez au projet."  msgid "The %(app_name)s team"  msgstr "L'équipe %(app_name)s" -#: templates/base.html:29 +#: templates/base.html:30  msgid "Logged in"  msgstr "Connecté" -#: templates/base.html:30 +#: templates/base.html:31  msgid "Log out"  msgstr "Déconnexion" -#: templates/base.html:31 +#: templates/base.html:32  msgid "Change password"  msgstr "Changement de mot de passe" -#: templates/base.html:33 templates/registration/activate.html:10 +#: templates/base.html:34 templates/registration/activate.html:10  #: templates/registration/login.html:8 templates/registration/login.html:10  #: templates/registration/password_reset_complete.html:8  msgid "Log in"  msgstr "Connexion" -#: templates/base.html:39 +#: templates/base.html:40  msgid "Lang"  msgstr "Langue" -#: templates/base.html:39 templates/base.html.py:86 +#: templates/base.html:40 templates/base.html.py:87  #: templates/ishtar/import_delete.html:10  msgid ":"  msgstr " :" -#: templates/base.html:64 +#: templates/base.html:65  msgid "Default selected items"  msgstr "Éléments sélectionnés par défaut" -#: templates/base.html:84 +#: templates/base.html:85  msgid "Current items"  msgstr "Éléments courants" @@ -1349,9 +1362,14 @@ msgstr "Ajout"  msgid "Ishtar administration"  msgstr "Administration d'Ishtar" -#: templates/blocks/JQueryJqGrid.html:4 -msgid "Search" -msgstr "Recherche" +#: templates/blocks/JQueryAdvancedTown.html:3 +msgctxt "Région" +msgid "State" +msgstr "Région" + +#: templates/blocks/JQueryAdvancedTown.html:29 +msgid "Choose a state first" +msgstr "Choisissez une région"  #: templates/blocks/JQueryJqGrid.html:10  msgid "Search and select an item" @@ -1381,14 +1399,10 @@ msgstr "Aide"  #: templates/ishtar/form.html:10 templates/ishtar/formset.html:8  #: templates/ishtar/import_list.html:55 templates/ishtar/merge.html:31 -#: templates/ishtar/wizard/confirm_wizard.html:40 -#: templates/ishtar/wizard/default_wizard.html:28 -#: templates/ishtar/wizard/default_wizard.html:44 -#: templates/ishtar/wizard/parcels_wizard.html:12 -#: templates/ishtar/wizard/parcels_wizard.html:27 +#: templates/ishtar/wizard/confirm_wizard.html:41 +#: templates/ishtar/wizard/default_wizard.html:29  #: templates/ishtar/wizard/search.html:13 -#: templates/ishtar/wizard/towns_wizard.html:12 -#: templates/ishtar/wizard/towns_wizard.html:32 +#: templates/ishtar/wizard/validation_bar.html:4  msgid "Validate"  msgstr "Valider" @@ -1461,6 +1475,36 @@ msgstr "A doublonne B"  msgid "Is not duplicate"  msgstr "N'est pas un doublon" +#: templates/ishtar/organization_person_form.html:9 +#: templates/ishtar/person_form.html:9 +msgid "Identification" +msgstr "Identification" + +#: templates/ishtar/organization_person_form.html:32 +#: templates/ishtar/person_form.html:43 +msgid "Modify" +msgstr "Modifier" + +#: templates/ishtar/organization_person_form.html:33 +#: templates/ishtar/person_form.html:44 +msgid "New" +msgstr "Nouveau" + +#: templates/ishtar/organization_person_form.html:34 +#: templates/ishtar/person_form.html:45 +msgid "Save" +msgstr "Enregistrer" + +#: templates/ishtar/organization_person_form.html:35 +#: templates/ishtar/person_form.html:46 +#: templates/ishtar/wizard/validation_bar.html:6 +msgid "Cancel" +msgstr "Annuler" + +#: templates/ishtar/person_form.html:24 +msgid "Contact informations" +msgstr "Coordonnées" +  #: templates/ishtar/sheet.html:21  msgid "Previous"  msgstr "Précédent" @@ -1642,22 +1686,17 @@ msgstr "Type d'utilisateur"  msgid "You have entered the following informations:"  msgstr "Vous avez entré les informations suivantes :" -#: templates/ishtar/wizard/confirm_wizard.html:39 +#: templates/ishtar/wizard/confirm_wizard.html:40  msgid "Would you like to save them?"  msgstr "Voulez-vous sauver ces informations ?" -#: templates/ishtar/wizard/default_wizard.html:34 -#: templates/ishtar/wizard/parcels_wizard.html:24 +#: templates/ishtar/wizard/default_wizard.html:35 +#: templates/ishtar/wizard/parcels_wizard.html:23  #: templates/ishtar/wizard/search.html:20 -#: templates/ishtar/wizard/towns_wizard.html:20 +#: templates/ishtar/wizard/towns_wizard.html:19  msgid "Add/Modify"  msgstr "Ajouter-Modifier" -#: templates/ishtar/wizard/default_wizard.html:45 -#: templates/ishtar/wizard/parcels_wizard.html:28 -msgid "Validate and end" -msgstr "Valider et confirmer" -  #: templates/ishtar/wizard/default_wizard.html:56  msgid ""  "The form has changed if you don't validate it all your changes will be lost." @@ -1665,18 +1704,22 @@ msgstr ""  "Le formulaire a changé. Si vous ne le validez pas, tous vos changements "  "seront perdus." -#: templates/ishtar/wizard/parcels_wizard.html:20 +#: templates/ishtar/wizard/parcels_wizard.html:19  msgid "all"  msgstr "tout" -#: templates/ishtar/wizard/parcels_wizard.html:23 +#: templates/ishtar/wizard/parcels_wizard.html:22  msgid "Add all parcels from the archaeological file"  msgstr "Ajouter toutes les parcelles du dossier archéologique associé" -#: templates/ishtar/wizard/towns_wizard.html:28 +#: templates/ishtar/wizard/towns_wizard.html:27  msgid "No town set in the associated file."  msgstr "Pas de commune dans le dossier associé." +#: templates/ishtar/wizard/validation_bar.html:5 +msgid "Validate and end" +msgstr "Valider et confirmer" +  #: templates/ishtar/wizard/wizard_closing_done.html:4  msgid "Item successfully closed"  msgstr "Élément clos avec succès" @@ -1962,9 +2005,6 @@ msgstr ""  #~ msgid "Activity:"  #~ msgstr "Activité :" -#~ msgid "Identification:" -#~ msgstr "Identification :" -  #~ msgid "Interpretation:"  #~ msgstr "Interpretation :" diff --git a/ishtar_common/management/commands/update_towns.py b/ishtar_common/management/commands/update_towns.py new file mode 100644 index 000000000..fa201b677 --- /dev/null +++ b/ishtar_common/management/commands/update_towns.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import csv +import datetime, time + +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError + +from ishtar_common import tasks + +class Command(BaseCommand): +    help = "Update department for french towns" + +    def handle(self, *args, **options): +        self.stdout.write("* Loading towns\n") +        self.stdout.flush() +        created, updated = tasks.update_towns() +        self.stdout.write("%d towns created, %s towns updated\n\n" % (created, +                                                                      updated)) +        self.stdout.flush() + diff --git a/ishtar_common/migrations/0030_auto__add_state__chg_field_sourcetype_txt_idx__chg_field_authortype_tx.py b/ishtar_common/migrations/0030_auto__add_state__chg_field_sourcetype_txt_idx__chg_field_authortype_tx.py new file mode 100644 index 000000000..3f7a2e498 --- /dev/null +++ b/ishtar_common/migrations/0030_auto__add_state__chg_field_sourcetype_txt_idx__chg_field_authortype_tx.py @@ -0,0 +1,371 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + +    def forwards(self, orm): +        # Adding model 'State' +        db.create_table('ishtar_common_state', ( +            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), +            ('label', self.gf('django.db.models.fields.CharField')(max_length=30)), +            ('number', self.gf('django.db.models.fields.CharField')(unique=True, max_length=3)), +        )) +        db.send_create_signal('ishtar_common', ['State']) + + +        # Changing field 'SourceType.txt_idx' +        db.alter_column('ishtar_common_sourcetype', 'txt_idx', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)) + +        # Changing field 'AuthorType.txt_idx' +        db.alter_column('ishtar_common_authortype', 'txt_idx', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)) + +        # Changing field 'PersonType.txt_idx' +        db.alter_column('ishtar_common_persontype', 'txt_idx', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)) +        # Adding field 'Department.state' +        db.add_column('ishtar_common_department', 'state', +                      self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ishtar_common.State'], null=True, blank=True), +                      keep_default=False) + + +        # Changing field 'OrganizationType.txt_idx' +        db.alter_column('ishtar_common_organizationtype', 'txt_idx', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)) + +        # Changing field 'SupportType.txt_idx' +        db.alter_column('ishtar_common_supporttype', 'txt_idx', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)) + +        # Changing field 'Format.txt_idx' +        db.alter_column('ishtar_common_format', 'txt_idx', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)) + +    def backwards(self, orm): +        # Deleting model 'State' +        db.delete_table('ishtar_common_state') + + +        # Changing field 'SourceType.txt_idx' +        db.alter_column('ishtar_common_sourcetype', 'txt_idx', self.gf('django.db.models.fields.CharField')(max_length=30, unique=True)) + +        # Changing field 'AuthorType.txt_idx' +        db.alter_column('ishtar_common_authortype', 'txt_idx', self.gf('django.db.models.fields.CharField')(max_length=30, unique=True)) + +        # Changing field 'PersonType.txt_idx' +        db.alter_column('ishtar_common_persontype', 'txt_idx', self.gf('django.db.models.fields.CharField')(max_length=30, unique=True)) +        # Deleting field 'Department.state' +        db.delete_column('ishtar_common_department', 'state_id') + + +        # Changing field 'OrganizationType.txt_idx' +        db.alter_column('ishtar_common_organizationtype', 'txt_idx', self.gf('django.db.models.fields.CharField')(max_length=30, unique=True)) + +        # Changing field 'SupportType.txt_idx' +        db.alter_column('ishtar_common_supporttype', 'txt_idx', self.gf('django.db.models.fields.CharField')(max_length=30, unique=True)) + +        # Changing field 'Format.txt_idx' +        db.alter_column('ishtar_common_format', 'txt_idx', self.gf('django.db.models.fields.CharField')(max_length=30, unique=True)) + +    models = { +        'auth.group': { +            'Meta': {'object_name': 'Group'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), +            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) +        }, +        'auth.permission': { +            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, +            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) +        }, +        'auth.user': { +            'Meta': {'object_name': 'User'}, +            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), +            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), +            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), +            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), +            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), +            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), +            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), +            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) +        }, +        'contenttypes.contenttype': { +            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, +            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) +        }, +        'ishtar_common.arrondissement': { +            'Meta': {'object_name': 'Arrondissement'}, +            'department': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Department']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}) +        }, +        'ishtar_common.author': { +            'Meta': {'object_name': 'Author'}, +            'author_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.AuthorType']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'person': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'author'", 'to': "orm['ishtar_common.Person']"}) +        }, +        'ishtar_common.authortype': { +            'Meta': {'object_name': 'AuthorType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) +        }, +        'ishtar_common.canton': { +            'Meta': {'object_name': 'Canton'}, +            'arrondissement': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Arrondissement']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}) +        }, +        'ishtar_common.department': { +            'Meta': {'ordering': "['number']", 'object_name': 'Department'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '30'}), +            'number': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '3'}), +            'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.State']", 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.documenttemplate': { +            'Meta': {'ordering': "['associated_object_name', 'name']", 'object_name': 'DocumentTemplate'}, +            'associated_object_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'template': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}) +        }, +        'ishtar_common.format': { +            'Meta': {'object_name': 'Format'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) +        }, +        'ishtar_common.formatertype': { +            'Meta': {'unique_together': "(('formater_type', 'options', 'many_split'),)", 'object_name': 'FormaterType'}, +            'formater_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'many_split': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), +            'options': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.globalvar': { +            'Meta': {'ordering': "['slug']", 'object_name': 'GlobalVar'}, +            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), +            'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.historicalorganization': { +            'Meta': {'ordering': "('-history_date', '-history_id')", 'object_name': 'HistoricalOrganization'}, +            'address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), +            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), +            'history_creator_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), +            'history_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), +            'history_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'history_modifier_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), +            'history_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'history_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), +            'id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'blank': 'True'}), +            'merge_key': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), +            'mobile_phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '300'}), +            'organization_type_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), +            'phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), +            'town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.import': { +            'Meta': {'object_name': 'Import'}, +            'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), +            'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), +            'error_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'imported_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), +            'importer_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.ImporterType']"}), +            'result_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'seconds_remaining': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'skip_lines': ('django.db.models.fields.IntegerField', [], {'default': '1'}), +            'state': ('django.db.models.fields.CharField', [], {'default': "'C'", 'max_length': '2'}), +            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.IshtarUser']"}) +        }, +        'ishtar_common.importercolumn': { +            'Meta': {'object_name': 'ImporterColumn'}, +            'col_number': ('django.db.models.fields.IntegerField', [], {'default': '1'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'importer_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'columns'", 'to': "orm['ishtar_common.ImporterType']"}), +            'regexp_pre_filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Regexp']", 'null': 'True', 'blank': 'True'}), +            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) +        }, +        'ishtar_common.importerdefault': { +            'Meta': {'object_name': 'ImporterDefault'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'importer_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'defaults'", 'to': "orm['ishtar_common.ImporterType']"}), +            'target': ('django.db.models.fields.CharField', [], {'max_length': '500'}) +        }, +        'ishtar_common.importerdefaultvalues': { +            'Meta': {'object_name': 'ImporterDefaultValues'}, +            'default_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'default_values'", 'to': "orm['ishtar_common.ImporterDefault']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'target': ('django.db.models.fields.CharField', [], {'max_length': '500'}), +            'value': ('django.db.models.fields.CharField', [], {'max_length': '500'}) +        }, +        'ishtar_common.importerduplicatefield': { +            'Meta': {'object_name': 'ImporterDuplicateField'}, +            'column': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'duplicate_fields'", 'to': "orm['ishtar_common.ImporterColumn']"}), +            'field_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) +        }, +        'ishtar_common.importertype': { +            'Meta': {'object_name': 'ImporterType'}, +            'associated_models': ('django.db.models.fields.CharField', [], {'max_length': '200'}), +            'description': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'is_template': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['ishtar_common.IshtarUser']", 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.importtarget': { +            'Meta': {'object_name': 'ImportTarget'}, +            'column': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'targets'", 'to': "orm['ishtar_common.ImporterColumn']"}), +            'formater_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.FormaterType']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'regexp_filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Regexp']", 'null': 'True', 'blank': 'True'}), +            'target': ('django.db.models.fields.CharField', [], {'max_length': '500'}) +        }, +        'ishtar_common.ishtaruser': { +            'Meta': {'object_name': 'IshtarUser', '_ormbases': ['auth.User']}, +            'person': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ishtaruser'", 'unique': 'True', 'to': "orm['ishtar_common.Person']"}), +            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) +        }, +        'ishtar_common.itemkey': { +            'Meta': {'object_name': 'ItemKey'}, +            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'importer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Import']", 'null': 'True', 'blank': 'True'}), +            'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) +        }, +        'ishtar_common.organization': { +            'Meta': {'object_name': 'Organization'}, +            'address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), +            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), +            'history_creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), +            'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'imports': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'imported_ishtar_common_organization'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['ishtar_common.Import']"}), +            'merge_candidate': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_candidate_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Organization']"}), +            'merge_exclusion': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_exclusion_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Organization']"}), +            'merge_key': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), +            'mobile_phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '300'}), +            'organization_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.OrganizationType']"}), +            'phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), +            'town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.organizationtype': { +            'Meta': {'ordering': "('label',)", 'object_name': 'OrganizationType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) +        }, +        'ishtar_common.person': { +            'Meta': {'object_name': 'Person'}, +            'address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'attached_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'members'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['ishtar_common.Organization']"}), +            'country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), +            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), +            'history_creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), +            'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'imports': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'imported_ishtar_common_person'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['ishtar_common.Import']"}), +            'merge_candidate': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_candidate_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Person']"}), +            'merge_exclusion': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_exclusion_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Person']"}), +            'merge_key': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), +            'mobile_phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'person_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['ishtar_common.PersonType']", 'symmetrical': 'False'}), +            'phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), +            'raw_name': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), +            'surname': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), +            'title': ('django.db.models.fields.CharField', [], {'max_length': '2', 'null': 'True', 'blank': 'True'}), +            'town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.persontype': { +            'Meta': {'ordering': "('label',)", 'object_name': 'PersonType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) +        }, +        'ishtar_common.regexp': { +            'Meta': {'object_name': 'Regexp'}, +            'description': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'regexp': ('django.db.models.fields.CharField', [], {'max_length': '500'}) +        }, +        'ishtar_common.sourcetype': { +            'Meta': {'object_name': 'SourceType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) +        }, +        'ishtar_common.state': { +            'Meta': {'ordering': "['number']", 'object_name': 'State'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '30'}), +            'number': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '3'}) +        }, +        'ishtar_common.supporttype': { +            'Meta': {'object_name': 'SupportType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) +        }, +        'ishtar_common.targetkey': { +            'Meta': {'unique_together': "(('target', 'value'),)", 'object_name': 'TargetKey'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'is_set': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'key': ('django.db.models.fields.TextField', [], {}), +            'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'keys'", 'to': "orm['ishtar_common.ImportTarget']"}), +            'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.town': { +            'Meta': {'ordering': "['numero_insee']", 'object_name': 'Town'}, +            'canton': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Canton']", 'null': 'True', 'blank': 'True'}), +            'center': ('django.contrib.gis.db.models.fields.PointField', [], {'srid': '27572', 'null': 'True', 'blank': 'True'}), +            'departement': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Department']", 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'numero_insee': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '6'}), +            'surface': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) +        } +    } + +    complete_apps = ['ishtar_common']
\ No newline at end of file diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 1dc4b6ced..fdad39c83 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -238,7 +238,7 @@ class GeneralType(models.Model):      """      label = models.CharField(_(u"Label"), max_length=100)      txt_idx = models.CharField(_(u"Textual ID"), -                         validators=[validate_slug], max_length=30, unique=True) +                         validators=[validate_slug], max_length=100, unique=True)      comment = models.TextField(_(u"Comment"), blank=True, null=True)      available = models.BooleanField(_(u"Available"), default=True)      HELP_TEXT = u"" @@ -365,7 +365,7 @@ class GeneralType(models.Model):              self.label = u" ".join(u" ".join(self.txt_idx.split('-')                                                          ).split('_')).title()          if not self.txt_idx: -            self.txt_idx = slugify(self.label) +            self.txt_idx = slugify(self.label)[:100]          # clean old keys          if self.pk: @@ -880,9 +880,22 @@ class DocumentTemplate(models.Model):          missing = ooo_replace(self.template, output_name, values)          return output_name +class State(models.Model): +    label = models.CharField(_(u"Label"), max_length=30) +    number = models.CharField(_(u"Number"), unique=True, max_length=3) + +    class Meta: +        verbose_name = _(u"State") +        ordering = ['number'] + +    def __unicode__(self): +        return self.label +  class Department(models.Model):      label = models.CharField(_(u"Label"), max_length=30)      number = models.CharField(_(u"Number"), unique=True, max_length=3) +    state = models.ForeignKey('State', verbose_name=_(u"State"), blank=True, +                              null=True)      class Meta:          verbose_name = _(u"Department") @@ -890,7 +903,7 @@ class Department(models.Model):          ordering = ['number']      def __unicode__(self): -        return u"%s (%s)" % (self.label, self.number) +        return self.label  class Address(BaseHistorizedItem):      address = models.TextField(_(u"Address"), null=True, blank=True) @@ -1564,10 +1577,13 @@ class Person(Address, Merge, OwnPerms, ValueGetter) :          values = [unicode(getattr(self, attr))                     for attr in ('surname', 'name')                                 if getattr(self, attr)] -        if not values: -            values = [self.raw_name or ""] +        if not values and self.raw_name: +            values = [self.raw_name]          if self.attached_to: -            values.append(u"- " + unicode(self.attached_to)) +            attached_to = unicode(self.attached_to) +            if values: +                values.append(u'-') +            values.append(attached_to)          return u" ".join(values)      def get_values(self, prefix=''): @@ -1601,6 +1617,9 @@ class Person(Address, Merge, OwnPerms, ValueGetter) :              self.merge_key += "-" + self.attached_to.merge_key          self.merge_key = self.merge_key[:300] +    def is_natural(self): +        return not self.attached_to +      def has_right(self, right_name):          if '.' in right_name:              right_name = right_name.split('.')[-1] @@ -1635,6 +1654,15 @@ class Person(Address, Merge, OwnPerms, ValueGetter) :                                 if getattr(self, attr)]          return slugify(u"-".join(values)) +    def save(self, *args, **kwargs): +        super(Person, self).save(*args, **kwargs) +        if hasattr(self, 'responsible_town_planning_service'): +            for fle in self.responsible_town_planning_service.all(): +                fle.save() # force update of raw_town_planning_service +        if hasattr(self, 'general_contractor'): +            for fle in self.general_contractor.all(): +                fle.save() # force update of raw_general_contractor +  class IshtarUser(User):      person = models.ForeignKey(Person, verbose_name=_(u"Person"), unique=True,                                 related_name='ishtaruser') diff --git a/ishtar_common/ooo_replace.py b/ishtar_common/ooo_replace.py index 4c487dd17..54ecfced4 100644 --- a/ishtar_common/ooo_replace.py +++ b/ishtar_common/ooo_replace.py @@ -23,6 +23,18 @@ from cStringIO import StringIO  from xml.etree.cElementTree import ElementTree, fromstring  from django.conf import settings +from ooo_translation import ooo_translation + +def translate_context(context, locale): +    if locale not in ooo_translation: +        return context +    new_context = {} +    for k in context: +        new_key = k +        if k in ooo_translation[locale]: +            new_key = ooo_translation[locale][k] +        new_context[new_key] = context[k] +    return new_context  OOO_NS = "{urn:oasis:names:tc:opendocument:xmlns:text:1.0}" @@ -135,13 +147,9 @@ def _custom_parsing(context, value, default_value=''):              value = re.sub(sub_exp % (pre_tag, base_key), v, value)      return value -def ooo_replace(infile, outfile, context, default_value=''): -    inzip = ZipFile(infile, 'r', ZIP_DEFLATED) -    outzip = ZipFile(outfile, 'w', ZIP_DEFLATED) +def _ooo_replace(content, context, missing_keys, default_value=''):      # regular ooo parsing -    content = ElementTree(fromstring(inzip.read('content.xml'))) -    missing_keys = set()      for xp in ('variable-set', 'variable-get'):          for p in content.findall(".//"+OOO_NS+xp):              name = p.get(OOO_NS+"name") @@ -167,12 +175,25 @@ def ooo_replace(infile, outfile, context, default_value=''):      content.write(str_io)      value = str_io.getvalue()      value = _custom_parsing(context, value, default_value).encode('utf-8') +    return value + +def ooo_replace(infile, outfile, context, default_value=''): +    inzip = ZipFile(infile, 'r', ZIP_DEFLATED) +    outzip = ZipFile(outfile, 'w', ZIP_DEFLATED) + +    values = {} +    missing_keys = set() +    for xml_file in ('content.xml', 'styles.xml'): +        content = ElementTree(fromstring(inzip.read(xml_file))) +        values[xml_file] = _ooo_replace(content, context, missing_keys, +                                        default_value)      for f in inzip.infolist(): -        if f.filename == 'content.xml': -            outzip.writestr('content.xml', value) +        if f.filename in values: +            outzip.writestr(f.filename, values[f.filename])          else:              outzip.writestr(f, inzip.read(f.filename)) +      inzip.close()      outzip.close()      return missing_keys diff --git a/ishtar_common/ooo_translation.py b/ishtar_common/ooo_translation.py new file mode 100644 index 000000000..a93a916ae --- /dev/null +++ b/ishtar_common/ooo_translation.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (C) 2013-2015 É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 +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# See the file COPYING for details. + +from django.conf import settings +from django.utils import translation +from django.utils.translation import pgettext_lazy + +# [('study', pgettext_lazy('ooo key', u'study')),] +TRANSLATION_STRINGS = [] + +ooo_translation = {} +cur_language = translation.get_language() + +try: +    for language, lbl in settings.LANGUAGES: +        translation.activate(language) +        ooo_translation[language] = {} +        for k, v in TRANSLATION_STRINGS: +            ooo_translation[language][k] = unicode(v) +finally: +    translation.activate(cur_language) diff --git a/ishtar_common/static/media/style.css b/ishtar_common/static/media/style.css index 5168cba6e..0505e21a5 100644 --- a/ishtar_common/static/media/style.css +++ b/ishtar_common/static/media/style.css @@ -3,7 +3,7 @@      background-color:#ff6e6e;  } -a.add-button, a.remove, +a.remove,  #progress-content{      background-color:#fff;  } @@ -16,16 +16,19 @@ div.form {      background-color: #922;  } +a.add-button, #reset_wizards{ +    background-color: #D14; +} +  /* color  */ +#window hr,  #context_menu .red,  a, a.remove {      color:#D14;  } -a.add-button{ -    color:#61615C; -} - +a.add-button, +#reset_wizards,  #window h1{      color:#fff;  } @@ -49,6 +52,11 @@ div.form {      border:0 solid #CCC;  } +#window hr{ +    border:0; +    border-bottom:1px solid #D14; +} +  /* shadows */  #progress-content,  .sheet{ @@ -58,6 +66,7 @@ div.form {  }  /* radius */ +a.button,  a.add-button, a.remove,  .sheet,  #progress-content, @@ -96,6 +105,10 @@ h3{      margin:1em 0 0.5em 0;  } +h4{ +    margin:10px 0; +} +  select{      max-width:550px;  } @@ -120,6 +133,15 @@ td{      text-align:left;  } +div.selected{ +    border:1px solid #922; +} + +textarea:disabled, +input[type="text"]:disabled{ +    background-color: #eee; +} +  button{      background-color:#f1f2f6;      border:1px solid #AAA; @@ -197,8 +219,7 @@ div#header{      width:100%;      text-align:left;      font-size: 0.9em; -    background-color: #f1f2f6; -    border-bottom:1px solid #CCC; +    background-color: #CCC;      margin-bottom:10px;      line-height:30px;      padding:0 20px; @@ -218,6 +239,26 @@ div#logo{      background-repeat:no-repeat;  } +div#validation-bar p{ +    margin:0; +} + +div#validation-bar input{ +    margin:0 30px; +} + +div#validation-bar.big{ +    height:60px; +} + +a.button{ +    padding:0.5em; +} + +#reset_wizards{ +    color: white; +} +  .display_details_inline,  .display_details{      display:inline-block; @@ -348,7 +389,7 @@ div#main_menu > ul > li{  div#content{      clear:both; -    margin:0 200px; +    margin:0 200px 70px 200px;      text-align:center;  } @@ -843,6 +884,7 @@ td.submit_button{  a.add-button, a.remove{      padding:0 4px; +    font-weight:bold;  }  a.remove{ @@ -897,6 +939,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/base.html b/ishtar_common/templates/base.html index 1ebebd815..92e61493b 100644 --- a/ishtar_common/templates/base.html +++ b/ishtar_common/templates/base.html @@ -19,6 +19,7 @@      <link type="text/css" href="{{JQUERY_UI_URL}}css/smoothness/jquery-ui.css" rel="stylesheet" />      <link rel="stylesheet" href="{{STATIC_URL}}/js/prettyPhoto/css/prettyPhoto.css" />      <link rel="stylesheet" href="{{STATIC_URL}}/media/style.css" /> +    {% include "blocks/extra_head.html" %}      {% block extra_head %}      {% endblock %}  </head> 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/templates/blocks/JQueryPersonOrganization.js b/ishtar_common/templates/blocks/JQueryPersonOrganization.js new file mode 100644 index 000000000..b13a2c28e --- /dev/null +++ b/ishtar_common/templates/blocks/JQueryPersonOrganization.js @@ -0,0 +1,50 @@ + +$("#id_select_{{field_id}}").autocomplete({ +    source: {{source}}, +    select: function( event, ui ) { +            var url =  {{edit_source}}; +            if(ui.item){ +                url = {{edit_source}}+ui.item.id; +                $('#id_{{field_id}}').val(ui.item.id); +            } else { +                $('#id_{{field_id}}').val(null); +            } +            $.get(url , function( data ) { +                $( "#div-{{field_id}}" ).html( data ); +            }); +        }, +    minLength: 2{% if options %}, +    {{options}} +    {% endif %} +}); + +$.get( {{edit_source}}{% if selected %}+'{{selected}}'{% endif %}, function( data ) { +    $( "#div-{{field_id}}" ).html( data ); +}); + +$('#id_select_{{field_id}}').live('click', function(){ +    $('#id_{{field_id}}').val(null); +    $('#id_select_{{field_id}}').val(null); +    $.get( {{edit_source}}, function( data ) { +        $( "#div-{{field_id}}" ).html( data ); +    }); +}); + +person_save_callback = function(item_id, lbl){ +    var url =  {{edit_source}}; +    $('#id_{{field_id}}').val(null); +    $('#id_select_{{field_id}}').val(lbl); +    if (item_id){ +        url = {{edit_source}}+item_id; +        $('#id_{{field_id}}').val(item_id); +    } +    $("#id_select_{{field_id}}").trigger('autocompletechange'); +    $.get(url , function( data ) { +        $( "#div-{{field_id}}" ).html( data ); +    }); +}; +person_new_callback = function(){ +    var url =  {{edit_source}}; +    $('#id_{{field_id}}').val(null); +    $('#id_select_{{field_id}}').val(null); +} diff --git a/ishtar_common/templates/blocks/PersonOrganization.html b/ishtar_common/templates/blocks/PersonOrganization.html new file mode 100644 index 000000000..6e7264c8e --- /dev/null +++ b/ishtar_common/templates/blocks/PersonOrganization.html @@ -0,0 +1,9 @@ +{% load i18n %}<input{{attrs_select}}/> +</table> + +<div id='div-{{name}}'></div> + +<input type="hidden"{{attrs_hidden}}/> +<script type="text/javascript"><!--// +{{js}} +//--></script> diff --git a/ishtar_common/templates/blocks/extra_head.html b/ishtar_common/templates/blocks/extra_head.html new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/ishtar_common/templates/blocks/extra_head.html @@ -0,0 +1,2 @@ + + diff --git a/ishtar_common/templates/ishtar/organization_form.html b/ishtar_common/templates/ishtar/organization_form.html new file mode 100644 index 000000000..207116c21 --- /dev/null +++ b/ishtar_common/templates/ishtar/organization_form.html @@ -0,0 +1,30 @@ +{% load i18n %}<table class='organization'> +<tr> +    <th><label>{{form.name.label}}</label></th> +    <td>{{form.name}}</td> +</tr> +</table> +{% for hidden in form.hidden_fields %}{{hidden}}{% endfor %} +<table class='organization-address'> +<tr> +    <th><label>{{form.organization_type.label}}</label></th> +    <td>{{form.organization_type}}</td> +</tr> +<tr> +    <th><label>{{form.address.label}}</label></th> +    <td>{{form.address}}</td> +</tr> +<tr> +    <th><label>{{form.address_complement.label}}</label></th> +    <td>{{form.address_complement}}</td> +</tr> +<tr> +    <th><label>{{form.postal_code.label}}</label></th> +    <td>{{form.postal_code}}</td> +</tr> +<tr> +    <th><label>{{form.town.label}}</label></th> +    <td>{{form.town}}</td> +</tr> +</table> + diff --git a/ishtar_common/templates/ishtar/organization_person_form.html b/ishtar_common/templates/ishtar/organization_person_form.html new file mode 100644 index 000000000..46f2cdc15 --- /dev/null +++ b/ishtar_common/templates/ishtar/organization_person_form.html @@ -0,0 +1,131 @@ +{% load i18n %}{% load url from future %} +<div id='orga-person-form'> +<form id='dyn-form-person' method='post'> +{% csrf_token %} +{% for hidden_field in form.hidden_fields %}{{hidden_field}}{% endfor %} +<input type='hidden' name='hidden_person_pk' id='hidden_person_pk' value='{{object.pk}}'/> +<input type='hidden' name='hidden_person_lbl' id='hidden_person_lbl' value="{{object}}"/> +<table class='organization'> +<caption>{% trans "Identification" %}</caption> +<tr> +    <th> </th> +    <td>{{form.attached_to}}</td> +</tr> +</table> + +<table class='person'> +<caption>{{relative_label}}</caption> +<tr> +    <th><label>{{form.title.label}}</label></th> +    <td>{{form.title}}</td> +</tr> +<tr> +    <th><label>{{form.name.label}}</label></th> +    <td>{{form.name}}</td> +</tr> +<tr> +    <th><label>{{form.surname.label}}</label></th> +    <td>{{form.surname}}</td> +</tr> +</table> +<div> +<input type='submit' id='btn-modify-person' value="{% trans "Modify"%}"/> +<input type='submit' id='btn-new-person' value="{% trans "New"%}"/> +<input type='submit' id='btn-save-person' value="{% trans "Save"%}"/> +<input type='submit' id='btn-cancel-person' value="{% trans "Cancel"%}"/> +</div> +</form> +</div> +<script type="text/javascript"><!--// + +function checkPendingRequest(todo) { +    window.setTimeout(function () { +        if ($.active > 0) { +            window.setTimeout(checkPendingRequest(todo), 250); +        } else { +            todo();  +        } +    }, 250); +}; + +person_save_callback = function(){ +    var item_id = $('#hidden_person_pk').val(); + +    var url = edit_url; +    $('#id_' + parent_id).val(null); +    if (item_id){ +        url = edit_url+item_id; +        $('#id_'+parent_id).val(item_id); +    } +    $("#id_select_"+parent_id).trigger('autocompletechange'); +    $.get(url , function( data ) { +        $( "#div-"+parent_id ).html( data ); +        var lbl = $('#hidden_person_lbl').val(); +        $('#id_select_' + parent_id).val(lbl); +    }); +}; + +person_new_callback = function(){ +    var url = edit_url; +    $('#id_'+parent_id).val(null); +    $('#id_select_'+parent_id).val(null); +} +$(function() { +    disable_person = function(){ +        $("#orga-person-form :input[type='text'], #orga-person-form textarea, #orga-person-form select").prop("disabled", true); +        $("#btn-save-person").hide(); +        $("#btn-cancel-person").hide(); +        $("#btn-modify-person").show(); +        $("#btn-new-person").show(); +        $("#orga-person-form").removeClass('selected'); +    } +    enable_person = function(){ +        $("#orga-person-form :input[type='text'], #orga-person-form textarea, #orga-person-form select").prop("disabled", false); +        $("#btn-save-person").show(); +        $("#btn-cancel-person").show(); +        $("#btn-modify-person").hide(); +        $("#btn-new-person").hide(); +        $("#orga-person-form").addClass('selected'); +    } + +    checkPendingRequest(disable_person); + +    $("#btn-modify-person").on('click', function(){ +        {% if object %}submit_url = "{% url 'organization_person_edit' object.pk %}"; +        {% else %}submit_url = "{% url 'organization_person_create' %}";{% endif %} +        enable_person(); +        return false; +    }); +    $("#btn-new-person").on('click', function(){ +        submit_url = "{% url 'organization_person_create' %}"; +        $("#orga-person-form").find("input[type=text], textarea, input[type=hidden]").val(""); +        enable_person(); +        person_new_callback(); +        return false; +    }); +    $("#btn-cancel-person").on('click', function(){ +        person_save_callback(); +        disable_person(); +        return false; +    }); +    $("#btn-save-person").on('click', function(){ +        var form = $("#dyn-form-person"); +        $.ajax({ +            url: submit_url, +            type: 'post', +            data: form.serialize(), +            dataType: 'html', +            success: function(data) { +                $("#orga-person-form").parent().html(data); +                person_save_callback(); +                disable_person(); +                return false; +            } +        }); +        return false; +    }); + + +}); +//--></script> + diff --git a/ishtar_common/templates/ishtar/person_form.html b/ishtar_common/templates/ishtar/person_form.html new file mode 100644 index 000000000..555aa1a5f --- /dev/null +++ b/ishtar_common/templates/ishtar/person_form.html @@ -0,0 +1,113 @@ +{% load i18n %}{% load url from future %} +<div id='person-form'> +<form id='dyn-form-person' method='post'> +{% csrf_token %} +{% for hidden_field in form.hidden_fields %}{{hidden_field}}{% endfor %} +<input type='hidden' name='hidden_person_pk' id='hidden_person_pk' value='{{object.pk}}'/> +<input type='hidden' name='hidden_person_lbl' id='hidden_person_lbl' value="{{object}}"/> +<table class='person'> +<caption>{% trans "Identification" %}</caption> +<tr> +    <th><label>{{form.title.label}}</label></th> +    <td>{{form.title}}</td> +</tr> +<tr> +    <th><label>{{form.name.label}}</label></th> +    <td>{{form.name}}</td> +</tr> +<tr> +    <th><label>{{form.surname.label}}</label></th> +    <td>{{form.surname}}</td> +</tr> +</table> +<table class='person-address'> +<caption>{% trans "Contact informations" %}</caption> +<tr> +    <th><label>{{form.address.label}}</label></th> +    <td>{{form.address}}</td> +</tr> +<tr> +    <th><label>{{form.address_complement.label}}</label></th> +    <td>{{form.address_complement}}</td> +</tr> +<tr> +    <th><label>{{form.postal_code.label}}</label></th> +    <td>{{form.postal_code}}</td> +</tr> +<tr> +    <th><label>{{form.town.label}}</label></th> +    <td>{{form.town}}</td> +</tr> +</table> +<div> +<input type='submit' id='btn-modify-person' value="{% trans "Modify"%}"/> +<input type='submit' id='btn-new-person' value="{% trans "New"%}"/> +<input type='submit' id='btn-save-person' value="{% trans "Save"%}"/> +<input type='submit' id='btn-cancel-person' value="{% trans "Cancel"%}"/> +</div> +</form> +</div> +<script type="text/javascript"><!--// +$(function() { +    // person_save_callback and person_new_callback must have been defined +    disable_person = function(){ +        $("#person-form :input[type='text'], #person-form textarea, #person-form select").prop("disabled", true); +        $("#btn-save-person").hide(); +        $("#btn-cancel-person").hide(); +        $("#btn-modify-person").show(); +        $("#btn-new-person").show(); +        $("#person-form").removeClass('selected'); +    } + +    enable_person = function(){ +        $("#person-form :input[type='text'], #person-form textarea, #person-form select").prop("disabled", false); +        $("#btn-save-person").show(); +        $("#btn-cancel-person").show(); +        $("#btn-modify-person").hide(); +        $("#btn-new-person").hide(); +        $("#person-form").addClass('selected'); +    } + +    disable_person(); + +    var submit_url; +    $("#btn-modify-person").on('click', function(){ +        {% if object %}submit_url = "{% url 'person_edit' object.pk %}"; +        {% else %}submit_url = "{% url 'person_create' %}";{% endif %} +        enable_person(); +        return false; +    }); +    $("#btn-new-person").on('click', function(){ +        submit_url = "{% url 'person_create' %}"; +        $("#person-form").find("input[type=text], textarea").val(""); +        enable_person(); +        person_new_callback(); +        return false; +    }); +    $("#btn-cancel-person").on('click', function(){ +        person_save_callback($('#hidden_person_pk').val(), +                             $('#hidden_person_lbl').val()); +        disable_person(); +        return false; +    }); +    $("#btn-save-person").on('click', function(){ +        var form = $("#dyn-form-person"); +        $.ajax({ +            url: submit_url, +            type: 'post', +            data: form.serialize(), +            dataType: 'html', +            success: function(data) { +                $("#person-form").parent().html(data); +                person_save_callback($('#hidden_person_pk').val(), +                                     $('#hidden_person_lbl').val()); +                disable_person(); +                return false; +            } +        }); +        return false; +    }); + + +}); +//--></script> diff --git a/ishtar_common/templates/ishtar/wizard/confirm_wizard.html b/ishtar_common/templates/ishtar/wizard/confirm_wizard.html index 0dd1f6f12..15bb9e9bd 100644 --- a/ishtar_common/templates/ishtar/wizard/confirm_wizard.html +++ b/ishtar_common/templates/ishtar/wizard/confirm_wizard.html @@ -36,8 +36,10 @@    </table>    {% endif %}    {% block "extra_informations" %}{% endblock %} +<div id='validation-bar' class='big'>    <p>{%if confirm_end_msg %}{{confirm_end_msg|safe}}{%else%}{% trans "Would you like to save them?" %}{%endif%}</p>    <input type="submit" value="{% trans "Validate" %}"/>  </div> +</div>  </form>  {% endblock %} diff --git a/ishtar_common/templates/ishtar/wizard/default_wizard.html b/ishtar_common/templates/ishtar/wizard/default_wizard.html index 3f2f3943e..a39037d10 100644 --- a/ishtar_common/templates/ishtar/wizard/default_wizard.html +++ b/ishtar_common/templates/ishtar/wizard/default_wizard.html @@ -1,5 +1,6 @@  {% extends "base.html" %}  {% load i18n range table_form %} +{% load url from future %}  {% block extra_head %}  {{form.media}}  {% endblock %} @@ -40,10 +41,9 @@  {% endif %}  <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />  {{ previous_fields|safe }} -<div id='validation-bar'> -  <input type="submit" id="submit_form" name='validate' value="{% trans "Validate" %}"/> -  {% if next_steps %}<input type="submit" id="submit_end_form" name='validate_and_end' value="{% trans "Validate and end" %}"/>{% endif %} -</div> +{% block "validation_bar" %} +{% include 'ishtar/wizard/validation_bar.html' %} +{% endblock %}  </div>  </form>  {% endblock %} diff --git a/ishtar_common/templates/ishtar/wizard/parcels_wizard.html b/ishtar_common/templates/ishtar/wizard/parcels_wizard.html index b550cf6c4..ce2bc9780 100644 --- a/ishtar_common/templates/ishtar/wizard/parcels_wizard.html +++ b/ishtar_common/templates/ishtar/wizard/parcels_wizard.html @@ -9,7 +9,6 @@  {{ wizard.form.media }}  {{ wizard.management_form }}    {{ wizard.form.management_form }} -<div class='top_button'><input type="submit" id="submit_form" value="{% trans "Validate" %}"/></div>    {%if wizard.form.non_form_errors%}  <table class='formset'>  <tr class='error'><th colspan='2'>{{wizard.form.non_form_errors}}</th></tr> @@ -24,8 +23,9 @@  <p><button name="formset_modify" value="{{wizard.steps.current}}">{% trans "Add/Modify" %}</button></p>  <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />  {{ previous_fields|safe }} -<input type="submit" id="submit_form" name='validate' value="{% trans "Validate" %}"/> -{% if next_steps %}<input type="submit" id="submit_end_form" name='validate_and_end' value="{% trans "Validate and end" %}"/>{% endif %} +{% block "validation_bar" %} +{% include 'ishtar/wizard/validation_bar.html' %} +{% endblock %}  </div>  </form>  {% endblock %} diff --git a/ishtar_common/templates/ishtar/wizard/towns_wizard.html b/ishtar_common/templates/ishtar/wizard/towns_wizard.html index 8e0b3551f..4f9ff6c5f 100644 --- a/ishtar_common/templates/ishtar/wizard/towns_wizard.html +++ b/ishtar_common/templates/ishtar/wizard/towns_wizard.html @@ -9,7 +9,6 @@  <div class='form'>  {% if TOWNS %}  {% if wizard.form.forms %} -<div class='top_button'><input type="submit" id="submit_form" value="{% trans "Validate" %}"/></div>  <table class='formset'>    {{ wizard.management_form }}    {%if wizard.form.non_form_errors%}<tr class='error'><th colspan='2'>{{wizard.form.non_form_errors}}</th></tr>{%endif%} @@ -29,7 +28,9 @@  {% endif %}  <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />  {{ previous_fields|safe }} -<input type="submit" id="submit_form" value="{% trans "Validate" %}"/> +{% block "validation_bar" %} +{% include 'ishtar/wizard/validation_bar.html' %} +{% endblock %}  </div>  </form>  {% endblock %} diff --git a/ishtar_common/templates/ishtar/wizard/validation_bar.html b/ishtar_common/templates/ishtar/wizard/validation_bar.html new file mode 100644 index 000000000..09907af67 --- /dev/null +++ b/ishtar_common/templates/ishtar/wizard/validation_bar.html @@ -0,0 +1,7 @@ +{% load i18n %} +{% load url from future %} +<div id='validation-bar'> +  <input type="submit" id="submit_form" name='validate' value="{% trans "Validate" %}"/> +  {% if next_steps %}<input type="submit" id="submit_end_form" name='validate_and_end' value="{% trans "Validate and end" %}"/>{% endif %} +  <a href='{% url 'reset_wizards' %}' id="reset_wizards" class='button'>{% trans "Cancel" %}</a> +</div> diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py index 97fe4f81a..13c19096e 100644 --- a/ishtar_common/urls.py +++ b/ishtar_common/urls.py @@ -33,12 +33,20 @@ urlpatterns = patterns('',         # internationalization         url(r'^i18n/', include('django.conf.urls.i18n')),         # General +       url(r'person_search/(?P<step>.+)?$', +           views.person_search_wizard, name='person_search'),         url(r'person_creation/(?P<step>.+)?$',             views.person_creation_wizard, name='person_creation'),         url(r'person_modification/(?P<step>.+)?$',             views.person_modification_wizard, name='person_modification'),         url(r'person_deletion/(?P<step>.+)?$',             views.person_deletion_wizard, name='person_deletion'), +       url(r'^person-edit/$', +           views.PersonCreate.as_view(), name='person_create'), +       url(r'^person-edit/(?P<pk>\d+)$', +           views.PersonEdit.as_view(), name='person_edit'), +       url(r'organization_search/(?P<step>.+)?$', +           views.organization_search_wizard, name='organization_search'),         url(r'organization_creation/(?P<step>.+)?$',             views.organization_creation_wizard, name='organization_creation'),         url(r'organization_modification/(?P<step>.+)?$', @@ -46,6 +54,16 @@ urlpatterns = patterns('',             name='organization_modification'),         url(r'organization_deletion/(?P<step>.+)?$',             views.organization_deletion_wizard, name='organization_deletion'), +       url(r'organization-edit/$', +           views.OrganizationCreate.as_view(), name='organization_create'), +       url(r'organization-edit/(?P<pk>\d+)$', +           views.OrganizationEdit.as_view(), name='organization_edit'), +       url(r'organization-person-edit/$', +           views.OrganizationPersonCreate.as_view(), +           name='organization_person_create'), +       url(r'organization-person-edit/(?P<pk>\d+)$', +           views.OrganizationPersonEdit.as_view(), +           name='organization_person_edit'),         url(r'account_management/(?P<step>.+)?$',             views.account_management_wizard, name='account_management'),         url(r'import/$', views.NewImportView.as_view(), name='new_import'), @@ -84,8 +102,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>[^/]+)/)?$', @@ -105,6 +127,7 @@ urlpatterns += patterns('ishtar_common.views',       url(r'person_merge/(?:(?P<page>\d+)/)?$', 'person_merge', name='person_merge'),       url(r'organization_merge/(?:(?P<page>\d+)/)?$', 'organization_merge',           name='organization_merge'), +     url(r'reset/$', 'reset_wizards', name='reset_wizards'),       url(r'(?P<action_slug>' + actions + r')/$', 'action', name='action'),  ) diff --git a/ishtar_common/views.py b/ishtar_common/views.py index 1b27bcf43..519332222 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -44,7 +44,7 @@ from django.shortcuts import render_to_response, redirect  from django.template import RequestContext, loader  from django.utils.decorators import method_decorator  from django.utils.translation import ugettext, ugettext_lazy as _ -from django.views.generic import ListView +from django.views.generic import ListView, UpdateView, CreateView  from django.views.generic.edit import CreateView, DeleteView  from xhtml2odt import xhtml2odt @@ -75,6 +75,11 @@ def index(request):          return render_to_response('index.html', dct,                                context_instance=RequestContext(request)) +person_search_wizard = wizards.SearchWizard.as_view([ +                        ('general-person_search', forms.PersonFormSelection)], +                        label=_(u"Person search"), +                        url_name='person_search',) +  person_creation_wizard = wizards.PersonWizard.as_view([                          ('identity-person_creation', forms.SimplePersonForm),                          ('person_type-person_creation', forms.PersonTypeForm), @@ -96,6 +101,11 @@ person_deletion_wizard = wizards.PersonDeletionWizard.as_view([                        label=_(u"Person deletion"),                        url_name='person_deletion',) +organization_search_wizard = wizards.SearchWizard.as_view([ +        ('general-organization_search', forms.OrganizationFormSelection)], +        label=_(u"Organization search"), +        url_name='organization_search',) +  organization_creation_wizard = wizards.OrganizationWizard.as_view([                      ('identity-organization_creation', forms.OrganizationForm),                      ('final-organization_creation', FinalForm)], @@ -202,6 +212,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): @@ -480,7 +529,7 @@ def get_item(model, func_name, default_name, extra_request_keys=[],      return func -def show_item(model, name): +def show_item(model, name, extra_dct=None):      def func(request, pk, **dct):          try:              item = model.objects.get(pk=pk) @@ -511,6 +560,9 @@ def show_item(model, name):                  if len(historized) > 1:                      dct['previous'] = historized[1].history_date          dct['item'], dct['item_name'] = item, name +        # add context +        if extra_dct: +            dct.update(extra_dct(request, item))          context_instance = RequestContext(request)          context_instance.update(dct)          n = datetime.datetime.now() @@ -790,6 +842,20 @@ def dashboard_main_detail(request, item_name):      return render_to_response('ishtar/dashboards/dashboard_main_detail.html',                              dct, context_instance=RequestContext(request)) +def reset_wizards(request): +    # dynamicaly execute each reset_wizards of each ishtar app +    for app in settings.INSTALLED_APPS: +        if app == 'ishtar_common': +            # no need for infinite recursion +            continue +        try: +            module = __import__(app) +        except ImportError: +            continue +        if hasattr(module, 'views') and hasattr(module.views, 'reset_wizards'): +            module.views.reset_wizards(request) +    return redirect(reverse('start')) +  ITEM_PER_PAGE = 20  def merge_action(model, form, key):      def merge(request, page=1): @@ -943,3 +1009,72 @@ class ImportDeleteView(IshtarMixin, LoginRequiredMixin, DeleteView):      def get_success_url(self):          return reverse('current_imports') + +class PersonCreate(LoginRequiredMixin, CreateView): +    model = models.Person +    form_class = forms.BasePersonForm +    template_name = 'ishtar/person_form.html' + +    def get_success_url(self): +        return reverse('person_edit', args=[self.object.pk]) + +class PersonEdit(LoginRequiredMixin, UpdateView): +    model = models.Person +    form_class = forms.BasePersonForm +    template_name = 'ishtar/person_form.html' + +    def get_success_url(self): +        return reverse('person_edit', args=[self.object.pk]) + +class OrganizationCreate(LoginRequiredMixin, CreateView): +    model = models.Organization +    form_class = forms.BaseOrganizationForm +    template_name = 'ishtar/organization_form.html' +    form_prefix = "orga" + +    def get_form_kwargs(self): +        kwargs = super(OrganizationCreate, self).get_form_kwargs() +        if hasattr(self.form_class, 'form_prefix'): +            kwargs.update({'prefix': self.form_class.form_prefix}) +        return kwargs + +class OrganizationEdit(LoginRequiredMixin, UpdateView): +    model = models.Organization +    form_class = forms.BaseOrganizationForm +    template_name = 'ishtar/organization_form.html' + +    def get_form_kwargs(self): +        kwargs = super(OrganizationEdit, self).get_form_kwargs() +        if hasattr(self.form_class, 'form_prefix'): +            kwargs.update({'prefix': self.form_class.form_prefix}) +        return kwargs + +class OrganizationPersonCreate(LoginRequiredMixin, CreateView): +    model = models.Person +    form_class = forms.BaseOrganizationPersonForm +    template_name = 'ishtar/organization_person_form.html' +    relative_label = _("Corporation manager") + +    def get_context_data(self, *args, **kwargs): +        data = super(OrganizationPersonCreate, self).get_context_data(*args, +                                                                      **kwargs) +        data['relative_label'] = self.relative_label +        return data + +    def get_success_url(self): +        return reverse('organization_person_edit', args=[self.object.pk]) + +class OrganizationPersonEdit(LoginRequiredMixin, UpdateView): +    model = models.Person +    form_class = forms.BaseOrganizationPersonForm +    template_name = 'ishtar/organization_person_form.html' +    relative_label = _("Corporation manager") + +    def get_context_data(self, *args, **kwargs): +        data = super(OrganizationPersonEdit, self).get_context_data(*args, +                                                                      **kwargs) +        data['relative_label'] = self.relative_label +        return data + +    def get_success_url(self): +        return reverse('organization_person_edit', args=[self.object.pk]) diff --git a/ishtar_common/widgets.py b/ishtar_common/widgets.py index 0ba4061a8..ba7e61e46 100644 --- a/ishtar_common/widgets.py +++ b/ishtar_common/widgets.py @@ -26,6 +26,7 @@ from django.db.models import fields  from django.forms import ClearableFileInput  from django.forms.widgets import flatatt  from django.template import Context, loader +from django.template.defaultfilters import slugify  from django.utils.encoding import smart_unicode  from django.utils.functional import lazy  from django.utils.html import escape @@ -33,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) @@ -203,7 +204,7 @@ class JQueryAutoComplete(forms.TextInput):              except:                  raise ValueError('source type is not valid')          dct = {'source':mark_safe(source), -               'field_id':field_id} +               'field_id':field_id,}          if self.options:              dct['options'] = mark_safe('%s' % self.options) @@ -218,6 +219,7 @@ class JQueryAutoComplete(forms.TextInput):      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...")          if value:              hiddens = []              selects = [] @@ -281,6 +283,204 @@ 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: +     * link between person and organization +     * display addresses of the person and of the organization +     * create new person and new organization +    """ + +    def __init__(self, source, edit_source, model, options={}, +                 attrs={}, new=False, limit={}, +                 html_template = 'blocks/PersonOrganization.html', +                 js_template='blocks/JQueryPersonOrganization.js'): +        self.options = None +        self.attrs = {} +        self.model = model +        self.source = source +        self.edit_source = edit_source +        if len(options) > 0: +            self.options = JSONEncoder().encode(options) +        self.attrs.update(attrs) +        self.new = new +        self.limit = limit +        self.js_template = js_template +        self.html_template = html_template + +    @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): +            encoded_src = "'%s'" % escape(source) +        else: +            try: +                encoded_src = "'" + unicode(source) + "'" +            except: +                raise ValueError('source type is not valid') +        return encoded_src + +    def render_js(self, field_id, selected=''): +        source = self.encode_source(self.source) +        edit_source = self.encode_source(self.edit_source) +        dct = {'source':mark_safe(source), +               'edit_source':mark_safe(edit_source), +               'selected':selected, +               'safe_field_id':slugify(field_id).replace('-', '_'), +               'field_id':field_id} +        if self.options: +            dct['options'] = mark_safe('%s' % self.options) +        js = loader.get_template(self.js_template).render(Context(dct)) +        return js + +    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 = '' +        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) +                if self.model: +                    try: +                        item = self.model.objects.get(pk=v) +                        selects[-1] = unicode(item) +                        selected = item.pk +                    except (self.model.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' +        new = '' +        dct = {'attrs_select':mark_safe(flatatt(attrs_select)), +               'attrs_hidden':mark_safe(flatatt(attrs_hidden)), +               'name':name, +               'js':self.render_js(name, selected), +               'new':mark_safe(new)} +        html = loader.get_template(self.html_template).render(Context(dct)) +        return html +  class JQueryJqGrid(forms.RadioSelect):      COL_TPL = "{name:'%(idx)s', index:'%(idx)s', sortable:true}"      class Media: diff --git a/ishtar_common/wizards.py b/ishtar_common/wizards.py index 190a7fc86..1e515d13e 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 @@ -617,6 +620,13 @@ class Wizard(NamedUrlWizardView):          return key in request.session[storage.prefix]['step_data'][form_key]      @classmethod +    def session_reset(cls, request, url_name): +        prefix = url_name + normalize_name(cls.__name__) +        storage = get_storage(cls.storage_name, prefix, request, +                              getattr(cls, 'file_storage', None)) +        storage.reset() + +    @classmethod      def session_set_value(cls, request, form_key, key, value, reset=False):          prefix = form_key.split('-')[1] + normalize_name(cls.__name__)          storage = get_storage(cls.storage_name, prefix, request, | 
