diff options
Diffstat (limited to 'ishtar_common')
31 files changed, 2925 insertions, 264 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 d8740b68c..23d7126c9 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -54,6 +54,14 @@ def get_town_field(label=_(u"Town"), required=True):           validators=[models.valid_id(models.Town)], label=label,           help_text=mark_safe(help_text), required=required) +def get_advanced_town_field(label=_(u"Town"), required=True): +    # !FIXME hard_link, reverse_lazy doen't seem to work with formsets +    return forms.IntegerField( +         widget=widgets.JQueryTown("/" + settings.URL_PATH + \ +                                   'autocomplete-advanced-town'), +         validators=[models.valid_id(models.Town)], label=label, +         required=required) +  def get_person_field(label=_(u"Person"), required=True, person_types=[]):      # !FIXME hard_link, reverse_lazy doen't seem to work with formsets      widget = None @@ -191,6 +199,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) @@ -242,6 +257,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 2a957a357..f9926d744 100644 --- a/ishtar_common/locale/fr/LC_MESSAGES/django.po +++ b/ishtar_common/locale/fr/LC_MESSAGES/django.po @@ -1,15 +1,15 @@  # Ishtar po translation. -# Copyright (C) 2010-2015 +# Copyright (C) 2010-2013  # This file is distributed under the same license as the Ishtar package. -# Étienne Loks <etienne.loks at peacefrogs net>, 2010-2015. +# Étienne Loks <etienne.loks at peacefrogs net>, 2010-2013.  #  #, fuzzy  msgid ""  msgstr ""  "Project-Id-Version: alpha\n"  "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-01-25 01:39+0100\n" -"PO-Revision-Date: 2015-01-25\n" +"POT-Creation-Date: 2014-12-31 12:21+0100\n" +"PO-Revision-Date: 2010-12-09\n"  "Last-Translator: Étienne Loks <etienne.loks at peacefrogs net>\n"  "Language-Team: \n"  "Language: \n" @@ -47,37 +47,38 @@ msgstr "Mobilier"  #: data_importer.py:117  #, 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."  +msgstr "" +"\"%(value)s\" est trop long. La longeur maximale est de %(length)d caractères."  #: data_importer.py:129  #, python-format  msgid "\"%(value)s\" not equal to yes or no" -msgstr "\"%(value)s\" diffère de oui ou non"  +msgstr "\"%(value)s\" n'est pas égal à oui ou non"  #: data_importer.py:140  #, python-format  msgid "\"%(value)s\" is not a float" -msgstr "\"%(value)s\" n'est pas un flottant" +msgstr "\"%(value)s\" n'est pas un chiffre à virgule"  #: data_importer.py:152 data_importer.py:164 data_importer.py:248  #, python-format  msgid "\"%(value)s\" is not a valid date" -msgstr "\"%(value)s\" n'est pas une date valide"  +msgstr "\"%(value)s\" n'est pas une date valide"  #: data_importer.py:175  #, python-format  msgid "\"%(value)s\" is not an integer" -msgstr "\"%(value)s\" n'est pas un entier"  +msgstr "\"%(value)s\" n'est pas un entier"  #: data_importer.py:203  #, 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"  +msgstr "Le choix pour \"%s\" n'est pas disponible. Lequel est pertinent ?\n"  #: data_importer.py:207  #, python-format  msgid "%d. None of the above" -msgstr "%d. Aucun de ceux-là" +msgstr "%d. Aucun des choix"  #: data_importer.py:260  msgid "" @@ -85,22 +86,22 @@ msgid ""  "check that column separator and encoding are similar to the ones used by the "  "reference file."  msgstr "" -"Le fichier fourni n'est pas correct. Vérifiez le format du fichier. " -"Si vous utilisez un fichier CSV : vérifiez que le séparateur de colonne et " -"l'encodage sont similaires à ceux du fichier de référence." +"Le fichier fourni n'est pas correct. Vérifiez le format du fichier. Si vous " +"utilisez un fichier CSV : vérifiez que les séparateurs de colonne et le " +"codage des caractères correspond au fichier de référence."  #: data_importer.py:264  #, 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" +msgstr "Trop de colonnes (%(user_col)d) quand le maximum est %(ref_col)d"  #: data_importer.py:266  msgid "No data provided" -msgstr "Aucune donnée fournie" +msgstr "Pas de donnée fourni"  #: data_importer.py:267  msgid "Value is required" -msgstr "Valeur requise" +msgstr "Une valeur est attendue"  #: data_importer.py:268  #, python-format @@ -109,7 +110,7 @@ msgstr "Au moins %d colonnes doivent être remplies"  #: data_importer.py:269  msgid "The regexp doesn't match." -msgstr "L'expression régulière ne fonctionne pas" +msgstr "L'espression régulière ne correspond pas."  #: data_importer.py:857  #, python-format @@ -120,13 +121,13 @@ 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:283 +#: forms.py:76 forms_common.py:331  msgid "Confirm"  msgstr "Confirmation"  #: forms.py:80  msgid "Are you sure you want to delete?" -msgstr "Êtes-vous sûr de vouloir supprimer ?" +msgstr "Êtes vous sûr de vouloir supprimer ?"  #: forms.py:88  msgid "There are identical items." @@ -134,7 +135,7 @@ msgstr "Il y a des éléments identiques."  #: forms.py:122 forms.py:123  msgid "Closing date" -msgstr "Date de clôture" +msgstr "Date de clotûre"  #: forms.py:127  msgid "You should select an item." @@ -148,8 +149,9 @@ msgstr "Ajouter un nouvel élément"  msgid "Template"  msgstr "Patron" -#: forms_common.py:43 forms_common.py:105 forms_common.py:186 -#: forms_common.py:191 models.py:829 models.py:1204 +#: forms_common.py:43 forms_common.py:57 forms_common.py:113 +#: forms_common.py:201 forms_common.py:206 models.py:842 models.py:1223 +#: 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" @@ -167,74 +169,74 @@ msgstr ""  "<p>Tapez le nom, le numéro de département et - ou le code postal de la "  "commune que vous voulez sélectionner. La recherche n'est pas sensible à la "  "casse.</p>\n" -"<p>Seuls les vingt premiers résultats sont affichés mais en plus du nom, " +"<p>Seuls les vingt premiers résultats sont affichés mais en plus du nom "  "préciser le numéro de département est généralement suffisant pour obtenir le "  "résultat souhaité.</p>\n"  "<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:471 ishtar_menu.py:40 models.py:1005 -#: models.py:1089 models.py:1129 templates/ishtar/sheet_person.html:6 +#: forms_common.py:65 forms_common.py:519 ishtar_menu.py:40 models.py:1018 +#: models.py:1108 models.py:1148 templates/ishtar/sheet_person.html:6  msgid "Person"  msgstr "Individu" -#: forms_common.py:93 forms_common.py:153 ishtar_menu.py:55 models.py:945 +#: forms_common.py:101 forms_common.py:168 ishtar_menu.py:55 models.py:958  #: templates/ishtar/sheet_organization.html:6  msgid "Organization"  msgstr "Organisation" -#: forms_common.py:95 forms_common.py:131 forms_common.py:149 -#: forms_common.py:178 models.py:779 models.py:911 models.py:940 models.py:995 -#: models.py:1190 templates/ishtar/sheet_organization.html:12 +#: forms_common.py:103 forms_common.py:139 forms_common.py:164 +#: forms_common.py:193 models.py:779 models.py:924 models.py:953 +#: models.py:1008 models.py:1209 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:97 models.py:893 +#: forms_common.py:105 models.py:906  msgid "Organization type"  msgstr "Type d'organisation" -#: forms_common.py:99 forms_common.py:180 models.py:824 +#: forms_common.py:107 forms_common.py:195 models.py:837  #: 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:101 forms_common.py:182 models.py:825 +#: forms_common.py:109 forms_common.py:197 models.py:838  #: 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:103 forms_common.py:184 models.py:827 +#: forms_common.py:111 forms_common.py:199 models.py:840  #: 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:106 forms_common.py:187 models.py:830 +#: forms_common.py:114 forms_common.py:202 models.py:843  msgid "Country"  msgstr "Pays" -#: forms_common.py:108 forms_common.py:151 forms_common.py:189 -#: forms_common.py:241 models.py:835 templates/ishtar/sheet_person.html:15 +#: forms_common.py:116 forms_common.py:166 forms_common.py:204 +#: forms_common.py:289 models.py:848 templates/ishtar/sheet_person.html:15  msgid "Email"  msgstr "Courriel" -#: forms_common.py:109 forms_common.py:190 models.py:832 +#: forms_common.py:117 forms_common.py:205 models.py:845  #: 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:110 models.py:833 +#: forms_common.py:118 models.py:846  #: 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:132 forms_common.py:152 models.py:942 models.py:1153 +#: forms_common.py:140 forms_common.py:167 models.py:955 models.py:1172  #: templates/sheet_ope.html:85 templates/sheet_ope.html.py:105  #: templates/sheet_ope.html:126 templates/ishtar/sheet_organization.html:27  #: templates/ishtar/sheet_person.html:42 templates/ishtar/sheet_person.html:95 @@ -242,121 +244,121 @@ msgstr "Téléphone portable"  msgid "Type"  msgstr "Type" -#: forms_common.py:140 +#: forms_common.py:148  msgid "Organization search"  msgstr "Recherche d'organisations" -#: forms_common.py:150 forms_common.py:176 models.py:993 +#: forms_common.py:165 forms_common.py:191 models.py:1006  #: templates/ishtar/sheet_organization.html:26  #: templates/ishtar/sheet_person.html:13  msgid "Surname"  msgstr "Prénom" -#: forms_common.py:164 +#: forms_common.py:179  msgid "Person search"  msgstr "Recherche d'individus" -#: forms_common.py:173 +#: forms_common.py:188  msgid "Identity"  msgstr "Identité" -#: forms_common.py:175 forms_common.py:428 models.py:992 models.py:1152 +#: forms_common.py:190 forms_common.py:476 models.py:1005 models.py:1171  #: 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:193 +#: forms_common.py:208  msgid "Current organization"  msgstr "Organisation actuelle" -#: forms_common.py:200 forms_common.py:223 forms_common.py:226 models.py:978 +#: forms_common.py:248 forms_common.py:271 forms_common.py:274 models.py:991  msgid "Person type"  msgstr "Type d'individu" -#: forms_common.py:236 forms_common.py:240 +#: forms_common.py:284 forms_common.py:288  msgid "Account"  msgstr "Compte" -#: forms_common.py:243 wizards.py:938 +#: forms_common.py:291 wizards.py:941  msgid "New password"  msgstr "Nouveau mot de passe" -#: forms_common.py:247 +#: forms_common.py:295  msgid "New password (confirmation)"  msgstr "Nouveau mot de passe (confirmation)" -#: forms_common.py:265 +#: forms_common.py:313  msgid "Your password and confirmation password do not match."  msgstr "La vérification du mot de passe a échoué." -#: forms_common.py:270 +#: forms_common.py:318  msgid "You must provide a correct password."  msgstr "Vous devez fournir un mot de passe correct." -#: forms_common.py:278 +#: forms_common.py:326  msgid "This username already exists."  msgstr "Ce nom d'utilisateur existe déjà." -#: forms_common.py:284 +#: forms_common.py:332  msgid "Send the new password by email?"  msgstr "Envoyer le nouveau mot de passe par courriel ?" -#: forms_common.py:292 forms_common.py:304 models.py:1205 +#: forms_common.py:340 forms_common.py:352 models.py:1224  #: templates/ishtar/sheet_person.html:72  msgid "Towns"  msgstr "Communes" -#: forms_common.py:301 +#: forms_common.py:349  msgid "There are identical towns."  msgstr "Il y a des communes identiques." -#: forms_common.py:380 +#: forms_common.py:428  msgid "Only one choice can be checked." -msgstr "Seul un choix peut être coché." +msgstr "Seul un choix peut être sélectionné." -#: forms_common.py:426 +#: forms_common.py:474  msgid "Documentation informations"  msgstr "Information sur le document" -#: forms_common.py:430 forms_common.py:449 models.py:1148 +#: forms_common.py:478 forms_common.py:497 models.py:1167  msgid "Source type"  msgstr "Type de source" -#: forms_common.py:432 models.py:1157 +#: forms_common.py:480 models.py:1176  msgid "Numerical ressource (web address)"  msgstr "Ressource numérique (adresse web)" -#: forms_common.py:433 models.py:1159 +#: forms_common.py:481 models.py:1178  msgid "Receipt date"  msgstr "Date de réception" -#: forms_common.py:435 models.py:925 models.py:1161 +#: forms_common.py:483 models.py:938 models.py:1180  msgid "Creation date"  msgstr "Date de création" -#: forms_common.py:446 forms_common.py:465 forms_common.py:496 models.py:1134 +#: forms_common.py:494 forms_common.py:513 forms_common.py:544 models.py:1153  #: templates/ishtar/wizard/wizard_person_deletion.html:124  msgid "Author"  msgstr "Auteur" -#: forms_common.py:458 +#: forms_common.py:506  msgid "Would you like to delete this documentation?" -msgstr "Voulez-vous supprimer ce document ?" +msgstr "Voulez vous supprimer ce document ?" -#: forms_common.py:472 models.py:1125 models.py:1131 +#: forms_common.py:520 models.py:1144 models.py:1150  msgid "Author type"  msgstr "Type d'auteur" -#: forms_common.py:490 +#: forms_common.py:538  msgid "Author selection"  msgstr "Sélection d'auteur" -#: forms_common.py:502 +#: forms_common.py:550  msgid "There are identical authors."  msgstr "Il y a des auteurs identiques." -#: forms_common.py:506 models.py:1135 models.py:1154 +#: forms_common.py:554 models.py:1154 models.py:1173  #: templates/sheet_ope.html:106  #: templates/ishtar/blocks/window_tables/documents.html:7  msgid "Authors" @@ -366,7 +368,7 @@ msgstr "Auteurs"  msgid "Administration"  msgstr "Administration" -#: ishtar_menu.py:30 views.py:122 +#: ishtar_menu.py:30 views.py:126  msgid "Account management"  msgstr "Gestion des comptes" @@ -388,11 +390,11 @@ msgstr "Modification"  #: ishtar_menu.py:48 ishtar_menu.py:65 templates/ishtar/merge.html:5  msgid "Merge" -msgstr "Fusion" +msgstr "Rapprochement" -#: ishtar_menu.py:51 ishtar_menu.py:68 widgets.py:81 +#: ishtar_menu.py:51 ishtar_menu.py:68 widgets.py:82  msgid "Delete" -msgstr "Suppression" +msgstr "Supprimer"  #: models.py:148  msgid "Not a valid item." @@ -406,7 +408,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:222 models.py:812 +#: models.py:222 models.py:812 models.py:823  msgid "Label"  msgstr "Libellé" @@ -450,7 +452,7 @@ msgstr "Variables globales"  msgid "Total"  msgstr "Total" -#: models.py:691 models.py:813 +#: models.py:691 models.py:813 models.py:824  #: templates/ishtar/dashboards/dashboard_main_detail.html:135  #: templates/ishtar/dashboards/dashboard_main_detail_users.html:26  msgid "Number" @@ -472,209 +474,208 @@ msgstr "Patron de document"  msgid "Document templates"  msgstr "Patrons de documents" -#: models.py:816 +#: models.py:816 models.py:825 models.py:937 +msgid "State" +msgstr "Région" + +#: models.py:829 templates/blocks/JQueryAdvancedTown.html:12  msgid "Department"  msgstr "Département" -#: models.py:817 +#: models.py:830  msgid "Departments"  msgstr "Départements" -#: models.py:842 +#: models.py:855  msgid "Merge key" -msgstr "Clé de fusion" +msgstr "Clé de rapprochement" -#: models.py:894 +#: models.py:907  msgid "Organization types"  msgstr "Types d'organisation" -#: models.py:897 templates/ishtar/dashboards/dashboard_main_detail.html:61 +#: models.py:910 templates/ishtar/dashboards/dashboard_main_detail.html:61  msgid "Created"  msgstr "Créé" -#: models.py:898 +#: models.py:911  msgid "Analyse in progress"  msgstr "Analyse en cours" -#: models.py:899 +#: models.py:912  msgid "Analysed" -msgstr "Analysé" +msgstr "Analisé" -#: models.py:900 +#: models.py:913  msgid "Import pending"  msgstr "Import en attente" -#: models.py:901 +#: models.py:914  msgid "Import in progress"  msgstr "Import en cours" -#: models.py:902 +#: models.py:915  msgid "Finished" -msgstr "Terminé" +msgstr "Fini" -#: models.py:906 -#, fuzzy +#: models.py:919  msgid "Archaeological files - SRA Pays de la Loire" -msgstr "Dossiers archéologiques" +msgstr "Dossiers archéologiques - SRA Pays de la Loire" -#: models.py:907 +#: models.py:920  msgid "Custom" -msgstr "" +msgstr "Personnalisé" -#: models.py:914 +#: models.py:927  msgid "Imported file"  msgstr "Fichier importé" -#: models.py:916 +#: models.py:929  msgid "Error file" -msgstr "Fichier erreur" +msgstr "Fichier d'erreur" -#: models.py:919 +#: models.py:932  msgid "Result file" -msgstr "Fichier résultant" +msgstr "Fichier de résultat" -#: models.py:922 +#: models.py:935  msgid "Importer type" -msgstr "" +msgstr "Type d'importeur" -#: models.py:924 -msgid "State" -msgstr "État" - -#: models.py:927 +#: models.py:940  msgid "End date"  msgstr "Date de fin" -#: models.py:929 +#: models.py:942  msgid "Seconds remaining"  msgstr "Secondes restantes" -#: models.py:932 +#: models.py:945  msgid "Import"  msgstr "Import" -#: models.py:933 +#: models.py:946  msgid "Imports"  msgstr "Imports" -#: models.py:946 +#: models.py:959  msgid "Organizations"  msgstr "Organisations" -#: models.py:948 +#: models.py:961  msgid "Can view all Organization"  msgstr "Peut voir toutes les Organisations" -#: models.py:949 +#: models.py:962  msgid "Can view own Organization"  msgstr "Peut voir sa propre Organisation" -#: models.py:950 +#: models.py:963  msgid "Can add own Organization"  msgstr "Peut ajouter sa propre Organisation" -#: models.py:951 +#: models.py:964  msgid "Can change own Organization"  msgstr "Peut changer sa propre Organisation" -#: models.py:952 +#: models.py:965  msgid "Can delete own Organization"  msgstr "Peut supprimer sa propre Organisation" -#: models.py:975 +#: models.py:988  msgid "Groups"  msgstr "Groupes" -#: models.py:979 +#: models.py:992  msgid "Person types"  msgstr "Types d'individu" -#: models.py:984 +#: models.py:997  msgid "Mr"  msgstr "M." -#: models.py:985 +#: models.py:998  msgid "Miss"  msgstr "Mlle" -#: models.py:986 +#: models.py:999  msgid "Mr and Miss"  msgstr "M. et Mme" -#: models.py:987 +#: models.py:1000  msgid "Mrs"  msgstr "Mme" -#: models.py:988 +#: models.py:1001  msgid "Doctor"  msgstr "Dr." -#: models.py:997 +#: models.py:1010  msgid "Raw name" -msgstr "" +msgstr "Nom brut" -#: models.py:999 models.py:1036 +#: models.py:1012 models.py:1052  msgid "Types"  msgstr "Types" -#: models.py:1002 +#: models.py:1015  msgid "Is attached to"  msgstr "Est rattaché à" -#: models.py:1006 +#: models.py:1019  msgid "Persons"  msgstr "Individus" -#: models.py:1008 +#: models.py:1021  msgid "Can view all Person"  msgstr "Peut voir toutes les Personnes" -#: models.py:1009 +#: models.py:1022  msgid "Can view own Person"  msgstr "Peut voir sa propre Personne" -#: models.py:1010 +#: models.py:1023  msgid "Can add own Person"  msgstr "Peut ajouter sa propre Personne" -#: models.py:1011 +#: models.py:1024  msgid "Can change own Person"  msgstr "Peut changer sa propre Personne" -#: models.py:1012 +#: models.py:1025  msgid "Can delete own Person"  msgstr "Peut supprimer sa propre Personne" -#: models.py:1093 +#: models.py:1112  msgid "Ishtar user"  msgstr "Utilisateur d'Ishtar" -#: models.py:1094 +#: models.py:1113  msgid "Ishtar users"  msgstr "Utilisateurs d'Ishtar" -#: models.py:1126 +#: models.py:1145  msgid "Author types"  msgstr "Types d'auteur" -#: models.py:1149 +#: models.py:1168  msgid "Source types"  msgstr "Types de source" -#: models.py:1162 templates/ishtar/sheet_person.html:40 +#: models.py:1181 templates/ishtar/sheet_person.html:40  #: templates/ishtar/sheet_person.html:67  msgid "Ref."  msgstr "Réf." -#: models.py:1164 +#: models.py:1183  msgid "Internal reference"  msgstr "Référence interne" -#: models.py:1191 +#: models.py:1210  msgid "Surface (m²)" -msgstr "Surface (m²)" +msgstr "Area (m²)" -#: models.py:1192 templates/sheet_ope.html:46 templates/sheet_ope.html.py:107 +#: models.py:1211 templates/sheet_ope.html:46 templates/sheet_ope.html.py:107  msgid "Localisation"  msgstr "Localisation" @@ -682,90 +683,98 @@ msgstr "Localisation"  msgid " (...)"  msgstr " (...)" -#: views.py:82 +#: views.py:86  msgid "New person"  msgstr "Nouvelle personne" -#: views.py:90 +#: views.py:94  msgid "Person modification"  msgstr "Modification d'une personne" -#: views.py:96 +#: views.py:100  msgid "Person deletion" -msgstr "Suppression de personne" +msgstr "Suppresion de personne" -#: views.py:102 +#: views.py:106  msgid "New organization"  msgstr "Nouvelle organisation" -#: views.py:109 +#: views.py:113  msgid "Organization modification"  msgstr "Modification d'une organisation" -#: views.py:115 +#: views.py:119  msgid "Organization deletion" -msgstr "Suppression d'une organisation" +msgstr "Suppresion d'une organisation" -#: views.py:212 +#: views.py:255  msgid "True"  msgstr "Oui" -#: views.py:214 +#: views.py:257  msgid "False"  msgstr "Non" -#: views.py:437 templates/base.html:75 +#: views.py:480 templates/base.html:75  #: 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:647 views.py:694 +#: views.py:693 views.py:740  msgid "Operation not permitted." -msgstr "Opération non permise." +msgstr "Opération non permise" -#: views.py:649 +#: views.py:695  #, python-format  msgid "New %s"  msgstr "Nouveau %s" -#: views.py:711 views.py:767 +#: views.py:757 views.py:813  msgid "Archaeological files"  msgstr "Dossiers archéologiques" -#: views.py:712 views.py:771 +#: views.py:758 views.py:817  msgid "Operations"  msgstr "Opérations" -#: views.py:714 views.py:776 +#: views.py:760 views.py:822  msgid "Context records"  msgstr "Unité d'Enregistrement" -#: views.py:716 views.py:781 +#: views.py:762 views.py:827  msgid "Finds"  msgstr "Mobilier" -#: widgets.py:326 +#: views.py:954 views.py:969 +msgid "Corporation manager" +msgstr "Représentant de la personne morale" + +#: widgets.py:196 widgets.py:300 widgets.py:415 +msgid "Search..." +msgstr "Recherche..." + +#: widgets.py:526  msgid "No results"  msgstr "Pas de résultats" -#: widgets.py:327 +#: widgets.py:527  msgid "Loading..."  msgstr "Chargement..." -#: widgets.py:328 +#: widgets.py:528  msgid "Remove"  msgstr "Enlever" -#: wizards.py:227 +#: wizards.py:230  msgid "Yes"  msgstr "Oui" -#: wizards.py:229 +#: wizards.py:232  msgid "No"  msgstr "Non" -#: wizards.py:992 +#: wizards.py:995  #, python-format  msgid "[%(app_name)s] Account creation/modification"  msgstr "[%(app_name)s] Ajout - modification du compte" @@ -907,7 +916,7 @@ msgstr "Opération fermée"  #: templates/sheet_ope.html:22  msgid "Closing date:" -msgstr "Date de clôture :" +msgstr "Date de clotûre :"  #: templates/sheet_ope.html:22  msgid "by" @@ -1078,7 +1087,7 @@ msgstr "Description"  #: templates/sheet_ope.html:142  msgid "No recording unit associated to this operation" -msgstr "Pas d'Unité d'Enregistrement associée à ce dossier" +msgstr "Pas d'Unité d'Enregistrement associée à ce dosser"  #: templates/window.html:37 templates/blocks/JQueryJqGrid.html:26  msgid "Add" @@ -1088,6 +1097,15 @@ msgstr "Ajout"  msgid "Ishtar administration"  msgstr "Administration d'Ishtar" +#: 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:4  msgid "Search"  msgstr "Recherche" @@ -1132,31 +1150,60 @@ msgstr "Valider"  #: templates/ishtar/merge.html:7  msgid "Every operation on this form is irreversible" -msgstr "" +msgstr "Chaque opération sur ce formulaire est irréversible"  #: templates/ishtar/merge.html:9  msgid "Page " -msgstr "" +msgstr "Page "  #: templates/ishtar/merge.html:17  msgid "Item A" -msgstr "" +msgstr "Élément A"  #: templates/ishtar/merge.html:18  msgid "Item B" -msgstr "" +msgstr "Élément B"  #: templates/ishtar/merge.html:19  msgid "B is a duplicate of A" -msgstr "" +msgstr "B doublonne A"  #: templates/ishtar/merge.html:20  msgid "A is a duplicate of B" -msgstr "" +msgstr "A doublonne B"  #: templates/ishtar/merge.html:21  msgid "Is not duplicate" -msgstr "" +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 +msgid "Cancel" +msgstr "Annuler" + +#: templates/ishtar/person_form.html:24 +msgid "Contact informations" +msgstr "Coordonnées"  #: templates/ishtar/sheet.html:21  msgid "Previous" @@ -1185,7 +1232,7 @@ msgstr "Personnes au sein de l'organisation"  #: templates/ishtar/sheet_organization.html:38  msgid "No person in this organization" -msgstr "Pas de personne au sein de cette organisation" +msgstr "Pas de personne au sein de l'organisation"  #: templates/ishtar/sheet_person.html:16  msgid "Type(s)" @@ -1345,7 +1392,7 @@ msgstr "Vous avez entré les informations suivantes :"  #: templates/ishtar/wizard/confirm_wizard.html:39  msgid "Would you like to save them?" -msgstr "Voulez-vous sauver ces informations ?" +msgstr "Voulez vous enregistrer ces informations ?"  #: templates/ishtar/wizard/default_wizard.html:34  #: templates/ishtar/wizard/parcels_wizard.html:24 @@ -1363,8 +1410,8 @@ msgstr "Valider et confirmer"  msgid ""  "The form has changed if you don't validate it all your changes will be lost."  msgstr "" -"Le formulaire a changé. Si vous ne le validez pas, tous vos changements seront " -"perdus." +"Le formulaire a changé. Si vous ne le validez pas tout vos changements vont " +"être perdus."  #: templates/ishtar/wizard/parcels_wizard.html:20  msgid "all" @@ -1380,7 +1427,7 @@ msgstr "Pas de commune dans le dossier associé."  #: templates/ishtar/wizard/wizard_closing_done.html:4  msgid "Item successfully closed" -msgstr "Élément clos avec succès" +msgstr "Élément clôt avec succès"  #: templates/ishtar/wizard/wizard_delete_done.html:4  msgid "Item successfully deleted" @@ -1497,11 +1544,11 @@ msgstr "Possède des parcelles"  #: templates/registration/activate.html:8  msgid "Account successfully activated" -msgstr "Compte créé avec succès" +msgstr "Compte crée avec succès"  #: templates/registration/activate.html:14  msgid "Account activation failed" -msgstr "La création du compte a échoué" +msgstr "La création du compte a échouée"  #: templates/registration/activation_complete.html:7  msgid "You may now login with your username and password." @@ -1579,7 +1626,7 @@ msgstr ""  #: templates/registration/password_reset_email.html:2  #, python-format  msgid "Reset password at %(site_name)s" -msgstr "Remise à zéro du mot de passe pour %(site_name)s" +msgstr "Remise à réro du mot de passe pour %(site_name)s"  #: templates/registration/password_reset_form.html:7  msgid "Reset password" @@ -1588,7 +1635,7 @@ msgstr "Réinitialisation du mot de passe"  #: templates/registration/registration_complete.html:6  msgid "You are now registered. Activation email sent."  msgstr "" -"Vous êtes maintenant enregistré. Un courriel d'activation de votre compte " +"Vous être maintenant enregistré. Un courriel d'activation de votre compte "  "vous a été envoyé."  #~ msgid "Wizard" @@ -1663,9 +1710,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 68c5b3b07..a1f1c1129 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) @@ -1557,10 +1570,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=''): @@ -1594,6 +1610,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] @@ -1628,6 +1647,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, | 
