def test_wildcard02(self):
        "Wildcard in first group + layout."
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name', required=False)
            last_name  = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell  = forms.CharField(label='Cell')
            fax   = forms.CharField(label='Fax')

        fbm = FieldBlockManager(
            {'id': 'names', 'label': 'Names', 'fields': '*', 'layout': LAYOUT_DUAL_SECOND},
            ('details', 'Details', ('phone', 'fax', 'cell')),
        )

        blocks = fbm.build(TestForm())
        name_block = blocks['names']
        self.assertEqual(LAYOUT_DUAL_SECOND, name_block.layout)
        self.assertListEqual(
            ['first_name', 'last_name'],
            [bfield.name for bfield in name_block.bound_fields]
        )
        self.assertListEqual(
            ['phone', 'fax', 'cell'],
            [bfield.name for bfield in blocks['details'].bound_fields]
        )
    def test_basic_get_item02(self):
        "Constructor with dicts."
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name', required=False)
            last_name  = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell  = forms.CharField(label='Cell')
            fax   = forms.CharField(label='Fax')

        template_details = 'creme_core/generics/blockform/field-block-IMPROVED.html'
        ctxt_details = {'some': 'info'}
        fbm = FieldBlockManager(
            {
                'id': 'names',
                'label': 'Names',
                'fields': ('first_name', 'last_name')
            }, {
                'id': 'details',
                'label': 'Details',
                'fields': ['cell', 'phone', 'fax'],
                'layout': LAYOUT_DUAL_FIRST,
                'template': template_details,
                'context': ctxt_details,
            },
        )
        form = TestForm()

        blocks = fbm.build(form)
        with self.assertNoException():
            names_group = blocks['names']

        self.assertIsInstance(names_group, BoundFieldBlocks.BoundFieldBlock)
        self.assertEqual('Names', names_group.label)
        self.assertEqual(LAYOUT_REGULAR, names_group.layout)
        self.assertEqual(
            'creme_core/generics/blockform/field-block.html',
            names_group.template_name,
        )
        self.assertIsNone(names_group.template_context)

        bfields = names_group.bound_fields
        self.assertEqual(2, len(bfields))

        bound_field1 = bfields[0]
        self.assertIsInstance(bound_field1, BoundField)
        self.assertEqual('first_name', bound_field1.name)

        self.assertEqual('last_name', bfields[1].name)

        # --
        details_group = blocks['details']
        self.assertEqual('Details', details_group.label)
        self.assertEqual(LAYOUT_DUAL_FIRST, details_group.layout)
        self.assertEqual(template_details, details_group.template_name)
        self.assertDictEqual(ctxt_details, details_group.template_context)
    def test_basic_get_item01(self):
        "Constructor with tuples."
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name', required=False)
            last_name  = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell  = forms.CharField(label='Cell')
            fax   = forms.CharField(label='Fax')

        fbm = FieldBlockManager(
            ('names',   'Names',   ('first_name', 'last_name')),
            ('details', 'Details', ['cell', 'phone', 'fax']),
        )
        form = TestForm()

        blocks = fbm.build(form)
        with self.assertNoException():
            names_group = blocks['names']

        self.assertIsInstance(names_group, BoundFieldBlocks.BoundFieldBlock)
        self.assertEqual('names', names_group.id)
        self.assertEqual('Names', names_group.label)
        self.assertEqual(LAYOUT_REGULAR, names_group.layout)

        names_bfields = names_group.bound_fields
        self.assertEqual(2, len(names_bfields))

        bound_field1 = names_bfields[0]
        self.assertIsInstance(bound_field1, BoundField)
        self.assertEqual('first_name', bound_field1.name)
        self.assertEqual('id_first_name', bound_field1.auto_id)

        # --
        self.assertEqual('last_name', names_bfields[1].name)

        # --
        with self.assertNoException():
            details_group = blocks['details']

        self.assertEqual('Details', details_group.label)
        self.assertListEqual(
            ['cell', 'phone', 'fax'],  # The order of the block info is used
            [bfield.name for bfield in details_group.bound_fields]
        )

        # ---
        with self.assertRaises(KeyError):
            # Already pop
            blocks['names']  # NOQA
    def test_new_wildcard02(self):
        "Extend with wildcard => error."
        fbm1 = FieldBlockManager(
            ('names', 'Names', ('first_name', 'last_name')),
        )

        with self.assertRaises(ValueError) as cm:
            fbm1.new(
                ('names', 'Names', '*'),
                ('details', 'Details', ('phone', 'fax')),
            )

        self.assertEqual(
            'You cannot extend with a wildcard (see the form-block with category "names")',
            str(cm.exception)
        )
    def test_init_error(self):
        with self.assertRaises(TypeError) as cm1:
            FieldBlockManager('names-Names-*')
        self.assertEqual(
            'Arguments <blocks> must be tuples or dicts.',
            str(cm1.exception)
        )

        with self.assertRaises(ValueError) as cm2:
            FieldBlockManager({
                'id': 'names', 'label': 'Names', 'fields': '*',
                'layout': 'invalid',  # <==
            })
        self.assertEqual(
            'The layout "invalid" is invalid.',
            str(cm2.exception)
        )
    def test_new01(self):
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name')
            last_name = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell = forms.CharField(label='Cell')
            fax = forms.CharField(label='Fax')

        names_id = 'names'
        details_id = 'details'
        fbm1 = FieldBlockManager(
            (names_id,   'Names', ('last_name', 'first_name')),
        )
        fbm2 = fbm1.new(
            (details_id, 'Details', ('cell', 'phone', 'fax')),
        )
        self.assertIsInstance(fbm2, FieldBlockManager)
        self.assertIsNot(fbm2, fbm1)

        form = TestForm()

        blocks = fbm2.build(form)
        with self.assertNoException():
            names_group = blocks[names_id]

        self.assertEqual('Names', names_group.label)
        self.assertListEqual(
            ['last_name', 'first_name'],
            [bfield.name for bfield in names_group.bound_fields]
        )

        with self.assertNoException():
            details_group = blocks[details_id]

        self.assertEqual('Details',      details_group.label)
        self.assertEqual(LAYOUT_REGULAR, details_group.layout)
        self.assertListEqual(
            ['cell', 'phone', 'fax'],
            [bfield.name for bfield in details_group.bound_fields],
        )

        self.assertListEqual(
            [names_id, details_id],
            [fb.id for fb in fbm2.build(form)],
        )
    def test_invalid_field01(self):
        class TestForm(forms.Form):
            last_name = forms.CharField(label='Last name')

        fbm = FieldBlockManager(
            ('names', 'Names', ('invalid', 'last_name')),
        )
        form = TestForm()

        with self.assertNoException():
            blocks = fbm.build(form)

        with self.assertNoException():
            group = blocks['names']

        self.assertListEqual(
            ['last_name'],
            [bfield.name for bfield in group.bound_fields],
        )
    def test_wildcard03(self):
        "Several wildcards => error."
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name')
            last_name  = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell  = forms.CharField(label='Cell')

        fbm = FieldBlockManager(
            ('names', 'Names', '*'),
            ('details', 'Details', '*'),
        )

        with self.assertRaises(ValueError) as cm:
            fbm.build(TestForm())

        self.assertEqual(
            f'Only one wildcard is allowed: {TestForm}',
            str(cm.exception)
        )
    def test_new_merge(self):
        "Block merge."
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name')
            last_name  = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell  = forms.CharField(label='Cell')
            fax   = forms.CharField(label='Fax')

        fbm1 = FieldBlockManager(
            ('names',   'Names',   ('last_name', 'first_name')),
            ('details', 'Details', ['cell']),
        )
        fbm2 = fbm1.new(
            ('details', 'Details extended', ('phone', 'fax')),
        )
        self.assertIsInstance(fbm2, FieldBlockManager)
        self.assertIsNot(fbm2, fbm1)

        form = TestForm()

        blocks = fbm2.build(form)
        with self.assertNoException():
            names_group = blocks['names']

        self.assertEqual('Names', names_group.label)
        self.assertListEqual(
            ['last_name', 'first_name'],
            [bfield.name for bfield in names_group.bound_fields],
        )

        with self.assertNoException():
            details_group = blocks['details']

        self.assertEqual('Details extended', details_group.label)
        self.assertListEqual(
            ['cell', 'phone', 'fax'],
            [bfield.name for bfield in details_group.bound_fields],
        )
    def test_new_order02(self):
        "Big <order> argument."
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name')
            last_name = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell = forms.CharField(label='Cell')
            sector = forms.CharField(label='Sector')
            position = forms.CharField(label='Position')

        corporate_id = 'corporate'
        names_id = 'names'
        details_id = 'details'
        fbm1 = FieldBlockManager(
            {
                'id': names_id,
                'label': 'Names',
                'fields': ('last_name', 'first_name'),
            },
        )
        fbm2 = fbm1.new(
            {
                'id': corporate_id,
                'label': 'Corporate',
                'fields': ['sector', 'position'],
                'order': 888,  # <====
            },
            {
                'id': details_id,
                'label': 'Details extended',
                'fields': ['phone', 'cell'],
                'order': 999,  # <====
            },
        )
        self.assertListEqual(
            [names_id, corporate_id, details_id],
            [fb.id for fb in fbm2.build(TestForm())],
        )
    def test_wildcard01(self):
        "Wildcard in second group."
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name', required=False)
            last_name  = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell  = forms.CharField(label='Cell')
            fax   = forms.CharField(label='Fax')

        fbm = FieldBlockManager(
            ('names',   'Names',   ('first_name', 'last_name')),
            ('details', 'Details', '*'),
        )

        blocks = fbm.build(TestForm())
        self.assertListEqual(
            ['first_name', 'last_name'],
            [bfield.name for bfield in blocks['names'].bound_fields],
        )
        self.assertListEqual(
            ['phone', 'cell', 'fax'],  # The order of the form-fields is used
            [bfield.name for bfield in blocks['details'].bound_fields],
        )
    def test_basic_iter(self):
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name', required=False)
            last_name  = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell  = forms.CharField(label='Cell')
            fax   = forms.CharField(label='Fax')

        fbm = FieldBlockManager(
            ('names',   'Names',   ('first_name', 'last_name')),
            ('details', 'Details', ('cell', 'phone', 'fax')),
        )
        form = TestForm()

        with self.assertNoException():
            blocks_list = [*fbm.build(form)]

        self.assertEqual(2, len(blocks_list))

        names_group = blocks_list[0]
        self.assertIsInstance(names_group, BoundFieldBlocks.BoundFieldBlock)
        self.assertEqual('Names', names_group.label)

        self.assertEqual('Details', blocks_list[1].label)
    def test_new_order03(self):
        "No <order> in __init__."
        with self.assertRaises(ValueError) as cm:
            FieldBlockManager(
                {
                    'id': 'names',
                    'label': 'Names',
                    'fields': ('last_name', 'first_name'),
                    'order': 0,  # <===
                },
            )

        self.assertEqual(
            'Do not pass <order> information in FieldBlockManager constructor.',
            str(cm.exception),
        )
 def test_new_error(self):
     fbm1 = FieldBlockManager(
         ('names', 'Names', ('last_name', 'first_name')),
     )
     with self.assertRaises(TypeError):
         fbm1.new('details-Details-*')
    def test_new_order01(self):
        "<order> argument."
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name')
            last_name = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell = forms.CharField(label='Cell')
            fax = forms.CharField(label='Fax')
            address = forms.CharField(label='Address')
            sector = forms.CharField(label='Sector')
            position = forms.CharField(label='Position')
            birthday = forms.DateField(label='Birthday')

        misc_id = 'misc'
        corporate_id = 'corporate'
        names_id = 'names'
        details_id = 'details'
        address_id = 'address'
        fbm1 = FieldBlockManager(
            {
                'id': misc_id,
                'label': 'Misc',
                'fields': ['birthday'],
            },
            {
                'id': details_id,
                'label': 'Details',
                'fields': ['cell'],
            },
            {
                'id': corporate_id,
                'label': 'Corporate',
                'fields': ['sector', 'position'],
            },
        )
        fbm2 = fbm1.new(
            {
                'id': address_id,
                'label': 'Address',
                'fields': ['address'],
                'order': 2,
            },
            {
                'id': details_id,  # We extend this one
                'label': 'Details extended',
                'fields': ['phone', 'fax'],
            },
            {
                'id': names_id,
                'label': 'Names',
                'fields': ('last_name', 'first_name'),
                'order': 0,
            },
        )
        self.assertListEqual(
            [
                names_id,  # order = 0
                misc_id,
                address_id,  # order = 2
                details_id,
                corporate_id,
            ],
            [fb.id for fb in fbm2.build(TestForm())],
        )
    def test_new_dictarg(self):
        "Dicts arguments."
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name')
            last_name  = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell  = forms.CharField(label='Cell')
            fax   = forms.CharField(label='Fax')
            address = forms.CharField(label='Address')

        fbm1 = FieldBlockManager(
            {
                'id': 'names',
                'label': 'Names',
                'fields': ('last_name', 'first_name'),
                'layout': LAYOUT_DUAL_FIRST,
            },
            ('details', 'Details', ['cell']),
        )
        fbm2 = fbm1.new(
            {
                'id': 'details',
                'label': 'Details extended',
                'fields': ('phone', 'fax'),
                'layout': LAYOUT_DUAL_SECOND,
            },
            {'id': 'address', 'label': 'Address', 'fields': ['address']},
            {
                'id': 'other',
                'label': 'Other',
                'fields': '*',
                'layout': LAYOUT_DUAL_FIRST,
            },
        )
        self.assertIsInstance(fbm2, FieldBlockManager)
        self.assertIsNot(fbm2, fbm1)

        form = TestForm()

        blocks = fbm2.build(form)
        with self.assertNoException():
            names_group = blocks['names']

        self.assertEqual('Names', names_group.label)
        self.assertEqual(LAYOUT_DUAL_FIRST, names_group.layout)
        self.assertListEqual(
            ['last_name', 'first_name'],
            [bfield.name for bfield in names_group.bound_fields]
        )

        with self.assertNoException():
            details_group = blocks['details']

        self.assertEqual('Details extended', details_group.label)
        self.assertEqual(LAYOUT_DUAL_SECOND, details_group.layout)
        self.assertListEqual(
            ['cell', 'phone', 'fax'],
            [bfield.name for bfield in details_group.bound_fields]
        )

        self.assertEqual(LAYOUT_REGULAR,    blocks['address'].layout)
        self.assertEqual(LAYOUT_DUAL_FIRST, blocks['other'].layout)
Exemple #17
0
    def test_form_gather_blocks_for_layout(self):
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name')
            last_name = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell = forms.CharField(label='Cell')
            fax = forms.CharField(label='Fax')
            address = forms.CharField(label='Address')

        form = TestForm()

        fbm1 = FieldBlockManager(('general', 'Information', '*'))
        self.assertGatheredEqual(
            [('regular', ['general'])],
            form_gather_blocks_for_layout(fbm1.build(form)),
        )

        fbm2 = FieldBlockManager(
            ('general', 'General', '*'),
            ('details', 'Details', ['phone', 'cell', 'fax']),
        )
        self.assertGatheredEqual(
            [('regular', ['general', 'details'])],
            form_gather_blocks_for_layout(fbm2.build(form)),
        )

        fbm3 = FieldBlockManager(
            {
                'id': 'names',
                'label': 'Names',
                'fields': ('last_name', 'first_name'),
                'layout': LAYOUT_DUAL_FIRST,
            },
            {
                'id': 'phones',
                'label': 'Phones',
                'fields': ('phone', 'cell'),
                'layout': LAYOUT_DUAL_SECOND,
            },
            ('other', 'Other', '*'),
        )
        self.assertGatheredEqual(
            [
                ('dual', (['names'], ['phones'])),
                ('regular', ['other']),
            ],
            form_gather_blocks_for_layout(fbm3.build(form)),
        )

        fbm4 = FieldBlockManager(
            {
                'id': 'names',
                'label': 'Names',
                'fields': ('last_name', 'first_name'),
                'layout': LAYOUT_DUAL_FIRST,
            },
            {
                'id': 'phones',
                'label': 'Phones',
                'fields': ('phone', 'cell'),
                'layout': LAYOUT_DUAL_SECOND,
            },
            {
                'id': 'address',
                'label': 'Address',
                'fields': ('address', ),
                'layout': LAYOUT_DUAL_FIRST,
            },
            ('other', 'Other', '*'),
        )
        self.assertGatheredEqual(
            [
                ('dual', (['names', 'address'], ['phones'])),
                ('regular', ['other']),
            ],
            form_gather_blocks_for_layout(fbm4.build(form)),
        )
Exemple #18
0
class EntityEmailForm(CremeEntityForm):
    """Mails are related to the selected contacts/organisations & the 'current' entity.
    Mails are send to selected contacts/organisations.
    """
    sender = EmailField(label=_('Sender'))

    c_recipients = MultiCreatorEntityField(label=_('Contacts'),      required=False, model=Contact,      q_filter={'email__gt': ''})
    o_recipients = MultiCreatorEntityField(label=_('Organisations'), required=False, model=Organisation, q_filter={'email__gt': ''})

    send_me = BooleanField(label=_('Send me a copy of this mail'), required=False)

    error_messages = {
        'no_person': _('Select at least a Contact or an Organisation'),
    }

    blocks = FieldBlockManager(
            ('recipients', _('Who'),  ['user', 'sender', 'send_me', 'c_recipients', 'o_recipients']),
            ('content',    _('What'), ['subject', 'body', 'body_html']),
            ('extra',      _('With'), ['signature', 'attachments']),
        )

    class Meta:
        model  = EntityEmail
        fields = ('sender', 'subject', 'body', 'body_html', 'signature', 'attachments')

    def __init__(self, entity, *args, **kwargs):
        # super(EntityEmailForm, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)
        self.entity = entity

        if isinstance(entity, (Contact, Organisation)):
            fn, msg = ('c_recipients', _('Beware: the contact «{}» has no email address!')) \
                      if isinstance(entity, Contact) else \
                      ('o_recipients', _('Beware: the organisation «{}» has no email address!'))
            field = self.fields[fn]

            if entity.email:
                field.initial = [entity.pk]
            else:
                field.help_text = msg.format(entity)

        self.user_contact = contact = self.user.linked_contact

        if contact.email:
            self.fields['sender'].initial = contact.email

        def finalize_recipient_field(name, model):
            if FieldsConfig.get_4_model(model).is_fieldname_hidden('email'):
                self.fields[name] = CharField(
                        label=self.fields[name].label,
                        required=False, widget=Label,
                        initial=ugettext('Beware: the field «Email address» is hidden ;'
                                         ' please contact your administrator.'
                                        ),
                    )

        finalize_recipient_field('c_recipients', Contact)
        finalize_recipient_field('o_recipients', Organisation)

    def _clean_recipients(self, field_name):
        if isinstance(self.fields[field_name].widget, Label):
            return []

        recipients = self.cleaned_data.get(field_name) or []
        bad_entities = []

        for entity in recipients:
            try:
                validate_email(entity.email)
            except ValidationError:
                bad_entities.append(entity)

        if bad_entities:
            msg_format = ugettext('The email address for {} is invalid')
            user = self.user

            for entity in bad_entities:
                self.add_error(field_name, msg_format.format(entity.allowed_str(user)))

        return recipients

    def clean_c_recipients(self):
        return self._clean_recipients('c_recipients')

    def clean_o_recipients(self):
        return self._clean_recipients('o_recipients')

    def clean(self):
        # cdata = super(EntityEmailForm, self).clean()
        cdata = super().clean()

        if not self._errors and not cdata['c_recipients'] and not cdata['o_recipients']:
            raise ValidationError(self.error_messages['no_person'], code='no_person')

        return cdata

    def save(self):
        cdata    = self.cleaned_data
        get_data = cdata.get

        sender      = get_data('sender')
        subject     = get_data('subject')
        body        = get_data('body')
        body_html   = get_data('body_html')
        signature   = get_data('signature')
        attachments = get_data('attachments')
        user        = get_data('user')

        sending_error = False

        def create_n_send_mail(recipient_address):
            nonlocal sending_error

            email = EntityEmail.create_n_send_mail(sender=sender, recipient=recipient_address,
                                                   subject=subject, user=user,
                                                   body=body, body_html=body_html,
                                                   signature=signature, attachments=attachments,
                                                  )

            if email.status == MAIL_STATUS_SENDINGERROR:
                sending_error = True

            return email

        with atomic():
            if get_data('send_me'):
                create_n_send_mail(sender)

            user_contact = self.user_contact
            create_relation = partial(Relation.objects.create, user=user)

            for recipient in chain(cdata['c_recipients'], cdata['o_recipients']):
                email = create_n_send_mail(recipient.email)

                create_relation(subject_entity=email, type_id=REL_SUB_MAIL_SENDED,   object_entity=user_contact)
                create_relation(subject_entity=email, type_id=REL_SUB_MAIL_RECEIVED, object_entity=recipient)

        if sending_error:
            entity_emails_send_type.refresh_job()
Exemple #19
0
class DeletionForm(CremeModelForm):
    blocks = FieldBlockManager({
        'id': 'general',
        'label': _('Replacement'),
        'fields': '*',
    })

    # TODO: what about deletion.DO_NOTHING ?!
    # TODO: manage deletion.RESTRICT (handler would need a smarter counting method)
    fk_handler_classes = {
        'django.db.models.deletion.CASCADE': CascadeHandler,
        'django.db.models.deletion.PROTECT': ProtectHandler,
        'django.db.models.deletion.SET_NULL': SetNullHandler,
        'django.db.models.deletion.SET_DEFAULT': SetDefaultHandler,
        '*SET(*)': SetHandler,
        'creme.creme_core.models.deletion.CREME_REPLACE_NULL':
        CremeReplaceNullHandler,
        'creme.creme_core.models.deletion.CREME_REPLACE': CremeReplaceHandler,
    }

    key_prefix = 'replace_'

    class Meta:
        model = DeletionCommand
        fields = ()

    def __init__(self,
                 instance_to_delete,
                 fk_handler_classes=None,
                 *args,
                 **kwargs):
        super().__init__(*args, **kwargs)
        self.instance_to_delete = instance_to_delete
        self.fields_configs = FieldsConfig.LocalCache()
        self.fk_handler_classes = fk_handler_classes or self.fk_handler_classes
        self.handlers = self._build_handlers()
        self._add_handlers_fields()

    def _add_handlers_fields(self):
        fields = self.fields

        for handler in self.handlers:
            form_field = handler.get_form_field()

            if form_field:
                fields[handler.key] = form_field

    def _build_handlers(self):
        handlers = []

        for field in type(
                self.instance_to_delete)._meta.get_fields(include_hidden=True):
            if field.one_to_many:
                handler_cls = self._get_fk_handler_class(model_field=field)
            elif field.many_to_many:
                handler_cls = self._get_m2m_handler_class(model_field=field)
            else:
                # TODO: manage other cases ?
                handler_cls = None

            if handler_cls is not None:
                related_field = field.field

                handlers.append(
                    handler_cls(
                        model_field=related_field,
                        model_field_hidden=self.fields_configs.get_4_model(
                            related_field.model).is_field_hidden(
                                related_field),
                        instance_to_delete=self.instance_to_delete,
                        key_prefix=self.key_prefix,
                    ))

        return handlers

    def _get_fk_handler_class(self, model_field):
        related_field = model_field.field

        related_model = related_field.model
        if related_model._meta.auto_created:
            # NB: we avoid the "internal" table of ManyToManyFields
            # TODO: better way ? (problem with custom 'through' table ?)
            return None

        # NB: we use the django's migration tool to get a string pattern
        #     of the attribute "on_delete".
        delete_signature = serializer_factory(
            related_field.remote_field.on_delete).serialize()[0]

        for handler_pattern, handler_cls in self.fk_handler_classes.items():
            if fnmatch(delete_signature, handler_pattern):
                return handler_cls

        raise ValueError(
            gettext('The field "{}.{}" cannot be deleted because its '
                    '"on_delete" constraint is not managed. '
                    'Please contact your administrator.'.format(
                        related_field.model.__name__,
                        related_field.name,
                    )))

    def _get_m2m_handler_class(self, model_field):
        # TODO: customisable behaviour ??
        return M2MHandler

    def clean(self):
        cdata = super().clean()

        if not self._errors:
            for handler in self.handlers:
                if handler.blocking:
                    self.add_error(handler.key, _('Deletion is not possible.'))

        return cdata

    def save(self, *args, **kwargs):
        instance = self.instance

        instance.instance_to_delete = self.instance_to_delete
        # TODO: improve CremeJSONEncoder to serialize iterators & remove list().
        get_data = self.cleaned_data.get
        instance.replacers = [
            *filter(None, (handler.replacer(get_data(handler.key) or None)
                           for handler in self.handlers)),
        ]
        instance.total_count = sum(handler.count for handler in self.handlers)
        instance.job = Job.objects.create(
            type_id=deletor_type.id,
            user=self.user,
        )

        # TODO: <instance_to_delete.is_deleted = True> if field exists

        return super().save(*args, **kwargs)
class UserSettingsConfigForm(CremeForm):
    url = URLField(label=_(u'Server URL'), required=False)
    url_examples = ChoiceField(
        label=_(u'Server URL examples'),
        required=False,
        help_text=_(u'Some common configurations'),
        choices=chain((("", ""), ), COMMONS_SERVER_URL_CFG),
        widget=Select(
            attrs={'onchange': 'this.form.url.value=$(this).val();'}),
    )
    domain = CharField(label=_(u'Domain'), required=False)
    ssl = ChoiceField(
        label=_(u'Is secure'),
        required=False,
        choices=(('', _('Default')), (1, _('Yes')), (0, _('No'))),
    )
    login = CharField(label=_(u'Login'), required=False)
    password = CharField(label=_(u'Password'),
                         required=False,
                         widget=PasswordInput)
    sync_calendars = TypedChoiceField(
        label=_(u'Synchronize activities (calendars)'),
        help_text=_(u'Choose if either you want to synchronize '
                    u'your activities in both way or not.'),
        choices=_BOOL_CHOICES,
        coerce=_choice_2_bool,
    )
    sync_contacts = TypedChoiceField(
        label=_(u'Synchronize contacts'),
        help_text=_(u'Choose if either you want to synchronize '
                    u'your contacts in both way or not.'),
        choices=_BOOL_CHOICES,
        coerce=_choice_2_bool,
    )

    blocks = FieldBlockManager(
        ('mobile_sync', _(u'Mobile synchronization configuration'), '*'),
        ('what_sync', _(u'What to sync'), ('sync_calendars', 'sync_contacts')),
    )

    def __init__(self, *args, **kwargs):
        super(UserSettingsConfigForm, self).__init__(*args, **kwargs)
        # user = self.user

        # self._svalues_cache = svalues_cache = {}
        # sv_get = SettingValue.objects.get

        # def get_svalue(key_id):
        #     sv = svalues_cache[key_id] = sv_get(key_id=key_id, user=user)
        #     return sv

        fields = self.fields

        user_settings = self.user.settings
        # sv_doesnotexist = SettingValue.DoesNotExist

        def_svalues = get_default_server_setting_values()
        let_empty_msg = ugettext(
            u"Let empty to get the default configuration (currently '%s').")

        url_field = fields['url']
        # url_field.help_text = let_empty_msg % sv_get(key_id=MAPI_SERVER_URL).value
        url_field.help_text = let_empty_msg % (def_svalues['url'].value or '')
        # try:
        #     url_field.initial = get_svalue(USER_MOBILE_SYNC_SERVER_URL).value
        # except sv_doesnotexist:
        #     pass
        url_field.initial = self._old_url = user_settings.get(
            user_msync_server_url_key, '')

        domain_field = fields['domain']
        # domain_field.help_text = let_empty_msg % sv_get(key_id=MAPI_DOMAIN).value
        domain_field.help_text = let_empty_msg % (def_svalues['domain'].value
                                                  or '')
        # try:
        #     domain_field.initial = get_svalue(USER_MOBILE_SYNC_SERVER_DOMAIN).value
        # except sv_doesnotexist:
        #     pass
        domain_field.initial = user_settings.get(user_msync_server_domain_key,
                                                 '')

        ssl_field = fields['ssl']
        ssl_field.help_text = ugettext(
            u"Let 'Default' to get the default configuration (currently '%s')."
        ) % (
            # ugettext('Yes') if sv_get(key_id=MAPI_SERVER_SSL).value else ugettext('No')
            ugettext('Yes') if def_svalues['ssl'].value else ugettext('No'))
        # try:
        #     ssl_field.initial = int(get_svalue(USER_MOBILE_SYNC_SERVER_SSL).value)
        # except sv_doesnotexist:
        #     pass
        ssl_field.initial = int(
            user_settings.get(user_msync_server_ssl_key, False))

        # ----------------------------------
        # try:
        #     fields['login'].initial = get_svalue(USER_MOBILE_SYNC_SERVER_LOGIN).value
        # except sv_doesnotexist:
        #     pass
        fields['login'].initial = self._old_login = user_settings.get(
            user_msync_server_login_key, '')

        pwd_field = fields['password']
        # try:
        #     pwd_field.initial = Cipher.decrypt_from_db(get_svalue(USER_MOBILE_SYNC_SERVER_PWD).value)
        #     pwd_field.widget.render_value = True
        # except sv_doesnotexist:
        #     pass
        try:
            pwd_field.initial = Cipher.decrypt_from_db(
                user_settings[user_msync_server_pwd_key])
        except KeyError:
            pass
        else:
            pwd_field.widget.render_value = True

        # try:
        #     fields['sync_calendars'].initial = int(get_svalue(USER_MOBILE_SYNC_ACTIVITIES).value)
        # except sv_doesnotexist:
        #     pass
        fields['sync_calendars'].initial = int(
            user_settings.get(user_msync_activities_key, False))

        # try:
        #     fields['sync_contacts'].initial = int(get_svalue(USER_MOBILE_SYNC_CONTACTS).value)
        # except sv_doesnotexist:
        #     pass
        fields['sync_contacts'].initial = int(
            user_settings.get(user_msync_contacts_key, False))

    def clean_ssl(self):
        try:
            return _choice_2_bool(self.cleaned_data['ssl'])
        except ValueError:
            pass

    def save(self):
        user = self.user
        clean_get = self.cleaned_data.get

        # url_is_created   = False
        # login_is_created = False

        # def upgrade_svalue(key_id, value):
        #     svalue = self._svalues_cache.get(key_id)
        #     created = False
        #
        #     if svalue is None:
        #         svalue = self._svalues_cache[key_id] \
        #                = SettingValue.objects.create(key_id=key_id, user=user, value=value)
        #         created = True
        #     elif svalue.value != value:
        #         svalue.value = value
        #         svalue.save()
        #
        #     return created
        #
        # def delete_svalue(key_id):
        #     svalue = self._svalues_cache.pop(key_id, None)
        #
        #     if svalue is not None:
        #         svalue.delete()

        url = clean_get('url')
        # if url:
        #     url_is_created = upgrade_svalue(USER_MOBILE_SYNC_SERVER_URL, url)
        # else:
        #     delete_svalue(USER_MOBILE_SYNC_SERVER_URL)

        domain = clean_get('domain')
        # if domain:
        #     upgrade_svalue(USER_MOBILE_SYNC_SERVER_DOMAIN, domain)
        # else:
        #     delete_svalue(USER_MOBILE_SYNC_SERVER_DOMAIN)

        ssl = clean_get('ssl')
        # if ssl is not None:
        #     upgrade_svalue(USER_MOBILE_SYNC_SERVER_SSL, ssl)
        # else:
        #     delete_svalue(USER_MOBILE_SYNC_SERVER_SSL)

        login = clean_get('login')
        # if login:
        #     login_is_created = upgrade_svalue(USER_MOBILE_SYNC_SERVER_LOGIN, login)
        # else:
        #     delete_svalue(USER_MOBILE_SYNC_SERVER_LOGIN)

        # upgrade_svalue(USER_MOBILE_SYNC_ACTIVITIES, clean_get('sync_calendars'))
        # upgrade_svalue(USER_MOBILE_SYNC_CONTACTS,   clean_get('sync_contacts'))

        password = clean_get('password')
        # if password:
        #     upgrade_svalue(USER_MOBILE_SYNC_SERVER_PWD, Cipher.encrypt_for_db(password))
        # else:
        #     delete_svalue(USER_MOBILE_SYNC_SERVER_PWD)

        with self.user.settings as user_settings:
            # TODO: factorise
            if url:
                user_settings[user_msync_server_url_key] = url
            else:
                user_settings.pop(user_msync_server_url_key, None)

            if domain:
                user_settings[user_msync_server_domain_key] = domain
            else:
                user_settings.pop(user_msync_server_domain_key, None)

            if ssl is not None:
                user_settings[user_msync_server_ssl_key] = ssl
            else:
                user_settings.pop(user_msync_server_ssl_key, None)

            if login:
                user_settings[user_msync_server_login_key] = login
            else:
                user_settings.pop(user_msync_server_login_key, None)

            user_settings[user_msync_activities_key] = clean_get(
                'sync_calendars')
            user_settings[user_msync_contacts_key] = clean_get('sync_contacts')

            if password:
                user_settings[
                    user_msync_server_pwd_key] = Cipher.encrypt_for_db(
                        password)
            else:
                user_settings.pop(user_msync_server_pwd_key, None)

        # TODO: test
        # TODO: add a true button to purge (ex: we could want to purge if the url is changed, or not)
        # if url_is_created or login_is_created:
        if self._old_url != url or self._old_login != login:
            try:
                as_client = CremeClient.objects.get(user=user)
            except CremeClient.DoesNotExist:
                pass
            else:
                as_client.purge(
                )  # NB: If server_url or login have changed, we reset all mapping & clientdef