class RTypeBrickItemAddCtypeForm(CremeModelForm): ctype = EntityCTypeChoiceField( label=_('Customised resource'), widget=DynamicSelect({'autocomplete': True}), ) class Meta: model = RelationBrickItem exclude = ('relation_type', ) def __init__(self, *args, **kwargs): # super(RTypeBrickItemAddCtypeForm, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs) instance = self.instance ct_field = self.fields['ctype'] compatible_ctypes = instance.relation_type.object_ctypes.all() if compatible_ctypes: ct_field.ctypes = compatible_ctypes used_ct_ids = frozenset( ct.id for ct, cells in instance.iter_cells()) # TODO: iter_ctypes() ?? ct_field.ctypes = (ct for ct in ct_field.ctypes if ct.id not in used_ct_ids) def save(self, *args, **kwargs): self.instance.set_cells(self.cleaned_data['ctype'], ()) # return super(RTypeBrickItemAddCtypeForm, self).save(*args, **kwargs) return super().save(*args, **kwargs)
def get_context(self, name, value, attrs): context = super().get_context(name=name, value=value, attrs=attrs) url = self.creation_url if url: final_attrs = context['widget']['attrs'] if final_attrs is None or not ('disabled' in final_attrs or 'readonly' in final_attrs): button_list = ActionButtonList( delegate=DynamicSelect(options=self.choices), attrs=self.attrs, ) allowed = self.creation_allowed label = str(self.creation_label) button_list.add_action( 'create', label, icon='add', title=label if allowed else gettext('Cannot create'), enabled=allowed, popupUrl=url, ) context = button_list.get_context(name=name, value=value, attrs=attrs) return context
class CustomBrickConfigItemCreateForm(CremeModelForm): ctype = EntityCTypeChoiceField( label=_('Related resource'), widget=DynamicSelect(attrs={'autocomplete': True}), ) class Meta(CremeModelForm.Meta): model = CustomBrickConfigItem def __init__(self, *args, **kwargs): # super(CustomBrickConfigItemCreateForm, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs) # TODO: add an 'exclude' argument in creme_entity_content_types() ?? get_for_model = ContentType.objects.get_for_model is_invalid = gui_bricks.brick_registry.is_model_invalid self.fields['ctype'].ctypes = ( get_for_model(model) for model in creme_registry.iter_entity_models() if not is_invalid(model)) def save(self, *args, **kwargs): instance = self.instance ct = self.cleaned_data['ctype'] instance.content_type = ct # super(CustomBrickConfigItemCreateForm, self).save(commit=False) super().save(commit=False) generate_string_id_and_save( CustomBrickConfigItem, [instance], 'creme_core-user_customblock_{}-{}'.format(ct.app_label, ct.model)) return instance
def _add_related_orga_fields(form): from django.contrib.contenttypes.models import ContentType from django.forms import ModelChoiceField from creme.creme_core.forms.widgets import DynamicSelect from creme.creme_core.models import RelationType fields = form.fields get_ct = ContentType.objects.get_for_model fields['organisation'] = ModelChoiceField( label=_('User organisation'), queryset=self.Organisation.objects.filter_managed_by_creme(), empty_label=None, ) fields['relation'] = ModelChoiceField( label=_('Position in the organisation'), queryset=RelationType.objects.filter( subject_ctypes=get_ct(self.Contact), object_ctypes=get_ct(self.Organisation), is_internal=False, ), empty_label=None, widget=DynamicSelect(attrs={'autocomplete': True}), initial=constants.REL_SUB_EMPLOYED_BY, ) def set_required(name): field = fields[name] field.required = field.widget.is_required = True set_required('first_name') set_required('last_name') set_required('email')
class CustomBrickConfigItemCreateForm(_CustomBrickConfigItemBaseForm): ctype = EntityCTypeChoiceField( label=_('Related resource'), widget=DynamicSelect(attrs={'autocomplete': True}), ) # class Meta(CremeModelForm.Meta): # model = CustomBrickConfigItem def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # TODO: add an 'exclude' argument in creme_entity_content_types() ?? get_for_model = ContentType.objects.get_for_model is_invalid = gui_bricks.brick_registry.is_model_invalid self.fields['ctype'].ctypes = ( get_for_model(model) for model in creme_registry.iter_entity_models() if not is_invalid(model)) def clean(self, *args, **kwargs): cdata = super().clean(*args, **kwargs) if not self._errors: self.instance.content_type = self.cleaned_data['ctype'] return cdata
class RTypeBrickAddForm(CremeModelForm): relation_type = ModelChoiceField( RelationType.objects, empty_label=None, widget=DynamicSelect(attrs={'autocomplete': True}), ) class Meta(CremeModelForm.Meta): model = RelationBrickItem def __init__(self, *args, **kwargs): # super(RTypeBrickAddForm, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs) existing_type_ids = RelationBrickItem.objects.values_list( 'relation_type_id', flat=True) relation_type = self.fields['relation_type'] relation_type.queryset = RelationType.objects.exclude( pk__in=existing_type_ids) def save(self, *args, **kwargs): self.instance.brick_id = gui_bricks.SpecificRelationsBrick.generate_id( 'creme_config', self.cleaned_data['relation_type'].id, ) # return super(RTypeBrickAddForm, self).save(*args, **kwargs) return super().save(*args, **kwargs)
class FieldsConfigAddForm(CremeForm): ctype = CTypeChoiceField(label=_(u'Related resource'), help_text=_(u'The proposed types of resource have ' u'at least a field which can be hidden.' ), widget=DynamicSelect(attrs={'autocomplete': True}), ) def __init__(self, *args, **kwargs): # super(FieldsConfigAddForm, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs) used_ct_ids = set(FieldsConfig.objects.values_list('content_type', flat=True)) self.ctypes = ctypes = [ct for ct in map(ContentType.objects.get_for_model, filter(FieldsConfig.is_model_valid, apps.get_models()) ) if ct.id not in used_ct_ids ] if ctypes: self.fields['ctype'].ctypes = ctypes else: # TODO: remove the 'submit' button ? self.fields['ctype'] = CharField( label=_(u'Related resource'), required=False, widget=Label, initial=_(u'All configurable types of resource are already configured.'), ) def save(self): if self.ctypes: return FieldsConfig.objects.create(content_type=self.cleaned_data['ctype'], descriptions=(), )
class EditCredentialsForm(CremeModelForm): can_view = BooleanField(label=_('Can view'), required=False) can_change = BooleanField(label=_('Can change'), required=False) can_delete = BooleanField(label=_('Can delete'), required=False) can_link = BooleanField( label=_('Can link'), required=False, help_text=_('You must have the permission to link on 2 entities' ' to create a relationship between them.'), ) can_unlink = BooleanField( label=_('Can unlink'), required=False, help_text=_('You must have the permission to unlink on 2 entities' ' to delete a relationship between them.'), ) set_type = ChoiceField( label=_('Type of entities set'), choices=SetCredentials.ESETS_MAP.items(), widget=CremeRadioSelect, ) ctype = EntityCTypeChoiceField( label=_('Apply to a specific type'), widget=DynamicSelect(attrs={'autocomplete': True}), required=False, empty_label=pgettext('content_type', 'None'), ) class Meta: model = SetCredentials exclude = ('role', 'value') # fields ?? def __init__(self, *args, **kwargs): # super(EditCredentialsForm, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs) fields = self.fields fields['ctype'].ctypes = filtered_entity_ctypes( self._get_allowed_apps()) # TODO: SetCredentials.value default to 0 value = self.instance.value or 0 fields['can_view'].initial = bool(value & EntityCredentials.VIEW) fields['can_change'].initial = bool(value & EntityCredentials.CHANGE) fields['can_delete'].initial = bool(value & EntityCredentials.DELETE) fields['can_link'].initial = bool(value & EntityCredentials.LINK) fields['can_unlink'].initial = bool(value & EntityCredentials.UNLINK) def _get_allowed_apps(self): return self.instance.role.allowed_apps def save(self, *args, **kwargs): get_data = self.cleaned_data.get self.instance.set_value(get_data('can_view'), get_data('can_change'), get_data('can_delete'), get_data('can_link'), get_data('can_unlink')) # return super(EditCredentialsForm, self).save(*args, **kwargs) return super().save(*args, **kwargs)
def test_options_generator(self): select = DynamicSelect(options=((id_, str(id_)) for id_ in range(10))) self.assertIsList(select.options) self.assertListEqual( [(id_, str(id_)) for id_ in range(10)], select.choices, )
def test_options_function(self): select = DynamicSelect( options=lambda: [(id, str(id)) for id in range(10)]) self.assertTrue(callable(select.options)) self.assertListEqual( [(id_, str(id_)) for id_ in range(10)], select.choices, )
def test_options_queryset(self): user = self.login() FakeContact.objects.create(last_name='Doe', first_name='John', user=user) select = DynamicSelect(options=FakeContact.objects.values_list('id', 'last_name')) self.assertIsInstance(select.options, QuerySet) self.assertListEqual(list(FakeContact.objects.values_list('id', 'last_name')), list(select.choices) )
class ContactUserCreationForm(UserCreation.form_class): organisation = ModelChoiceField( label=_('User organisation'), queryset=self.Organisation.objects.filter_managed_by_creme(), empty_label=None, ) relation = ModelChoiceField( label=_('Position in the organisation'), # NB: the QuerySet is built in __init__() because a loading artefact # makes ContentType values inconsistent in unit tests if the # Queryset is built here. queryset=RelationType.objects.none(), empty_label=None, widget=DynamicSelect(attrs={'autocomplete': True}), initial=constants.REL_SUB_EMPLOYED_BY, ) blocks = UserCreation.form_class.blocks.new({ 'id': 'contact', 'label': _('Related Contact'), 'fields': ('organisation', 'relation'), }) def __init__(this, *args, **kwargs): super().__init__(*args, **kwargs) fields = this.fields get_ct = ContentType.objects.get_for_model fields['relation'].queryset = RelationType.objects.filter( subject_ctypes=get_ct(self.Contact), symmetric_type__subject_ctypes=get_ct(self.Organisation), is_internal=False, ) for field_name in ('first_name', 'last_name', 'email'): field = fields[field_name] field.required = field.widget.is_required = True def save(this, *args, **kwargs): user = super().save(*args, **kwargs) cdata = this.cleaned_data Relation.objects.create( user=user, subject_entity=user.linked_contact, type=cdata['relation'], object_entity=cdata['organisation'], ) return user
class CustomFieldsCTAddForm(CustomFieldsBaseForm): content_type = EntityCTypeChoiceField( label=_('Related resource'), help_text=_('The other custom fields for this type of resource ' 'will be chosen by editing the configuration'), widget=DynamicSelect({'autocomplete': True})) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) used_ct_ids = { *CustomField.objects.values_list('content_type_id', flat=True) } ct_field = self.fields['content_type'] ct_field.ctypes = (ct for ct in ct_field.ctypes if ct.id not in used_ct_ids)
def test_render(self): select = DynamicSelect(options=[(1, 'A'), (2, 'B')]) self.assertHTMLEqual( '<select class="ui-creme-input ui-creme-widget widget-auto ui-creme-dselect" ' ' name="test" url="" widget="ui-creme-dselect">' ' <option value="1">A</option>' ' <option value="2" selected>B</option>' '</select>', select.render('test', 2)) Choice = DynamicSelect.Choice select = DynamicSelect(options=[ (Choice(1, disabled=True, help='disabled'), 'A'), (Choice(2, help='item B'), 'B'), (Choice(3, help='item C'), 'C'), (Choice(4, readonly=True, help='readonly'), 'D'), ], ) self.assertHTMLEqual( '<select class="ui-creme-input ui-creme-widget widget-auto ui-creme-dselect" ' ' name="test" url="" widget="ui-creme-dselect">' ' <option value="1" disabled help="disabled">A</option>' ' <option value="2" selected help="item B">B</option>' ' <option value="3" help="item C">C</option>' ' <option value="4" readonly help="readonly">D</option>' '</select>', select.render('test', 2))
class GraphFetcherField(Field): widget = DynamicSelect(attrs={'autocomplete': True}) default_error_messages = { 'invalid_choice': _( 'Select a valid choice. %(value)s is not one of the available choices.' ), } _graph: 'AbstractReportGraph' choice_iterator_class = FetcherChoiceIterator _choice_separator: str # Separate the type & the value of each fetcher choice def __init__(self, *, graph=None, choice_separator='|', **kwargs): super().__init__(**kwargs) self._choice_separator = choice_separator self.graph = graph def _update_choices(self): self.widget.choices = self.choice_iterator_class( graph=self._graph, separator=self._choice_separator, ) @property def choice_separator(self): return self._choice_separator @choice_separator.setter def choice_separator(self, sep): self._choice_separator = sep self._update_choices() @property def graph(self): return self._graph @graph.setter def graph(self, graph): self._graph = graph self._update_choices() def to_python(self, value): """Returns a GraphFetcher.""" if not value: return None fetcher_type_id, __, fetcher_value = value.partition(self._choice_separator) graph = self.graph fetcher = graph.fetcher_registry.get( graph=graph, fetcher_dict={ GraphFetcher.DICT_KEY_TYPE: fetcher_type_id, GraphFetcher.DICT_KEY_VALUE: fetcher_value, }, ) if fetcher.error: raise ValidationError( self.error_messages['invalid_choice'], code='invalid_choice', params={'value': value}, ) return fetcher
def test_options_list(self): select = DynamicSelect(options=[(1, 'A'), (2, 'B')]) self.assertIsList(select.options) self.assertListEqual([(1, 'A'), (2, 'B')], select.choices)
class GraphInstanceBrickForm(CremeForm): volatile_column = ChoiceField( label=_('Volatile column'), choices=(), required=False, widget=DynamicSelect(attrs={'autocomplete': True}), help_text=_( 'When the graph is displayed on the detail-view of an entity, ' 'only the entities linked to this entity by the following link ' 'are used to compute the graph.'), ) def __init__(self, graph, instance=None, *args, **kwargs): super().__init__(*args, **kwargs) self.graph = graph self.fields['volatile_column'].choices = self._get_volatile_choices( graph.linked_report.ct) def _get_volatile_choices(self, ct): choices = [] fk_choices = [('fk-' + name, vname) for name, vname in ModelFieldEnumerator( ct.model_class(), deep=0, only_leafs=False).filter( (lambda f, deep: isinstance(f, ForeignKey) and issubclass(f.remote_field.model, CremeEntity)), viewable=True, ).choices()] self._rtypes = {} rtype_choices = [] for rtype in RelationType.objects.compatible(ct, include_internals=True): rtype_choices.append(('rtype-' + rtype.id, str(rtype))) self._rtypes[rtype.id] = rtype if fk_choices: choices.append((_('Fields'), fk_choices)) if rtype_choices: choices.append((_('Relationships'), rtype_choices)) if not choices: choices.append(('', _('No available choice'))) else: choices.insert( 0, ('', pgettext_lazy('reports-volatile_choice', 'None'))) return choices def clean(self): cleaned_data = super().clean() volatile_column = cleaned_data.get('volatile_column') kwargs = {} if volatile_column: link_type, link_val = volatile_column.split('-', 1) if link_type == 'fk': kwargs['volatile_field'] = link_val else: kwargs['volatile_rtype'] = self._rtypes[link_val] try: self.ibci = self.graph.create_instance_brick_config_item( save=False, **kwargs) except InstanceBrickConfigItemError as e: raise ValidationError(str(e)) from e return cleaned_data def save(self): ibci = self.ibci ibci.save() return ibci
class FieldsConfigAddForm(CremeModelForm): ctype = core_fields.CTypeChoiceField( label=_('Related resource'), help_text= _('The proposed types of resource have at least a field which can be hidden.' ), widget=DynamicSelect(attrs={'autocomplete': True}), ) class Meta(CremeModelForm.Meta): model = FieldsConfig def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) models = [ *filter(FieldsConfig.objects.is_model_valid, apps.get_models()) ] # NB: we use <FieldsConfig.objects.get_for_models()> to take advantage of its cache ; # it useful because this constructor can be called several times in a request # because of our wizard (which fill the instance by calling all # previous steps' validation). # Old code: # used_ct_ids = {*FieldsConfig.objects.values_list('content_type', flat=True)} excluded_ct_ids = { # Do not want a choice "creme entity" ('description' can be hidden). ContentType.objects.get_for_model(CremeEntity).id, # Exclude ContentType which already have a configuration *( fc.content_type_id for fc in FieldsConfig.objects.get_for_models(models).values() if not fc._state.adding # <True> means the FieldsConfig is in DB ) } self.ctypes = ctypes = [ ct for ct in map(ContentType.objects.get_for_model, models) if ct.id not in excluded_ct_ids ] if ctypes: self.fields['ctype'].ctypes = ctypes else: # TODO: remove the 'submit' button ? self.fields['ctype'] = core_fields.ReadonlyMessageField( label=_('Related resource'), initial=_( 'All configurable types of resource are already configured.' ), ) def clean(self, *args, **kwargs): cdata = super().clean(*args, **kwargs) if not self._errors: instance = self.instance instance.content_type = self.cleaned_data['ctype'] instance.descriptions = () return cdata def save(self, *args, **kwargs): if self.ctypes: # NB: remove if we raise a ValidationError in clean() super().save(*args, **kwargs) return self.instance
class VcfImportForm(CremeModelForm): class Meta: model = Contact fields = ('user', 'civility', 'first_name', 'last_name', 'position') vcf_step = IntegerField(widget=HiddenInput) image_encoded = CharField(required=False, widget=HiddenInput) # Details phone = CharField(label=_('Phone number'), required=False) mobile = CharField(label=_('Mobile'), required=False) fax = CharField(label=_('Fax'), required=False) email = EmailField(label=_('Email address'), required=False) url_site = URLField(label=_('Web Site'), required=False) # Address homeaddr_name = CharField(label=_('Name'), required=False) homeaddr_address = CharField(label=_('Address'), required=False) homeaddr_city = CharField(label=_('City'), required=False) homeaddr_country = CharField(label=_('Country'), required=False) homeaddr_code = CharField(label=_('Zip code'), required=False) homeaddr_region = CharField(label=_('Region'), required=False) # Related Organisation create_or_attach_orga = BooleanField( label=_('Create or attach organisation'), required=False, initial=False, ) organisation = CreatorEntityField(label=_('Organisation'), required=False, model=Organisation) relation = ModelChoiceField( label=_('Position in the organisation'), queryset=RelationType.objects.none(), initial=REL_SUB_EMPLOYED_BY, required=False, empty_label='', widget=DynamicSelect(attrs={'autocomplete': True}), ) # TODO: Composite field update_work_name = BooleanField( label=_('Update name'), required=False, initial=False, help_text=_('Update organisation selected name')) update_work_phone = BooleanField( label=_('Update phone'), required=False, initial=False, help_text=_('Update organisation selected phone')) update_work_fax = BooleanField( label=_('Update fax'), required=False, initial=False, help_text=_('Update organisation selected fax')) update_work_email = BooleanField( label=_('Update email'), required=False, initial=False, help_text=_('Update organisation selected email')) update_work_url_site = BooleanField( label=_('Update web site'), required=False, initial=False, help_text=_('Update organisation selected web site')) update_work_address = BooleanField( label=_('Update address'), required=False, initial=False, help_text=_('Update organisation selected address')) # Organisation name & details work_name = CharField(label=_('Name'), required=False) work_phone = CharField(label=_('Phone number'), required=False) work_fax = CharField(label=_('Fax'), required=False) work_email = EmailField(label=_('Email address'), required=False) work_url_site = URLField(label=_('Web Site'), required=False) # Organisation address workaddr_name = CharField(label=_('Name'), required=False) workaddr_address = CharField(label=_('Address'), required=False) workaddr_city = CharField(label=_('City'), required=False) workaddr_country = CharField(label=_('Country'), required=False) workaddr_code = CharField(label=_('Zip code'), required=False) workaddr_region = CharField(label=_('Region'), required=False) error_messages = { 'required4orga': _('Required, if you want to create organisation'), 'no_orga_creation': _('Create organisation not checked'), 'orga_not_selected': _('Organisation not selected'), 'required2update': _('Required, if you want to update organisation'), } # Names of the fields corresponding to the Contact's details. contact_details = ['phone', 'mobile', 'fax', 'email', 'url_site'] # Names of the fields corresponding to the related Organisation (but not its Address). orga_fields = ['name', 'phone', 'email', 'fax', 'url_site'] # Correspondence between VCF field types & form-field names. phone_dict = { 'HOME': 'phone', 'CELL': 'mobile', 'FAX': 'fax', 'WORK': 'work_phone', } email_dict = { 'HOME': 'email', 'INTERNET': 'email', 'WORK': 'work_email', } url_dict = { 'HOME': 'url_site', 'INTERNET': 'url_site', 'WORK': 'work_url_site', } # Form-field names prefix for address + correspondence with VCF field types. address_prefixes = { 'HOME': HOME_ADDR_PREFIX, 'WORK': WORK_ADDR_PREFIX, } # Mapping between form fields names (which use vcf lib names) & Address fields names. address_mapping = [ ('name', 'name'), ('address', 'address'), ('city', 'city'), ('country', 'country'), ('code', 'zipcode'), ('region', 'department'), ] # blocks = CremeModelWithUserForm.blocks.new( blocks = CremeModelForm.blocks.new( ('details', _('Details'), contact_details), ('contact_address', _('Billing address'), [HOME_ADDR_PREFIX + n[0] for n in address_mapping]), ('organisation', _('Organisation'), [ 'create_or_attach_orga', 'organisation', 'relation', *chain.from_iterable( ('update_work_' + fn, 'work_' + fn) for fn in orga_fields) ]), ('organisation_address', _('Organisation billing address'), [ 'update_work_address', *(WORK_ADDR_PREFIX + n[0] for n in address_mapping) ]), ) type_help_text = _('Read in VCF File without type : ') other_help_text = _('Read in VCF File : ') def __init__(self, vcf_data=None, *args, **kwargs): super().__init__(*args, **kwargs) fields = self.fields if vcf_data: self._init_contact_fields(vcf_data) self._init_orga_field(vcf_data) self._init_addresses_fields(vcf_data) if vcf_data.contents.get('photo'): fields['image_encoded'].initial = vcf_data.photo.value.replace( '\n', '') # Beware: this queryset directly in the field declaration does not work on some systems in unit tests... # (it seems that the problem it caused by the M2M - other fields work, but why ???) fields['relation'].queryset = RelationType.objects.filter( subject_ctypes=_get_ct(Contact), object_ctypes=_get_ct(Organisation), ) self._hide_fields() def _hide_fields(self): fields = self.fields address_mapping = self.address_mapping fconfigs = FieldsConfig.get_4_models((Contact, Organisation, Address)) # TODO: use shipping address if not hidden ? if fconfigs[Contact].is_fieldname_hidden('billing_address'): prefix = HOME_ADDR_PREFIX for form_fname, __ in address_mapping: del fields[prefix + form_fname] is_orga_field_hidden = fconfigs[Organisation].is_fieldname_hidden for fname in [*fields ]: # NB: Cannot mutate the OrderedDict during iteration. # NB: 5 == len('work_') if fname.startswith('work_') and is_orga_field_hidden(fname[5:]): del fields[fname] del fields['update_' + fname] if is_orga_field_hidden('billing_address'): prefix = WORK_ADDR_PREFIX for form_fname, __ in address_mapping: del fields[prefix + form_fname] del fields['update_work_address'] is_addr_field_hidden = fconfigs[Address].is_fieldname_hidden addr_prefixes = self.address_prefixes.values() for form_fname, model_fname in address_mapping: if is_addr_field_hidden(model_fname): for prefix in addr_prefixes: fields.pop(prefix + form_fname, None) def _init_contact_fields(self, vcf_data): contents = vcf_data.contents fields = self.fields contact_data = contents.get('n') if contact_data: value = vcf_data.n.value last_name = value.family fields['first_name'].initial = value.given fields['last_name'].initial = last_name fields['homeaddr_name'].initial = last_name prefix = value.prefix if prefix: # TODO: find in title too ? civ = Civility.objects.filter( shortcut__icontains=prefix).first() if civ: fields['civility'].initial = civ.id else: fields[ 'civility'].help_text = self.other_help_text + prefix else: first_name, sep, last_name = vcf_data.fn.value.partition(' ') fields['first_name'].initial = first_name fields['last_name'].initial = last_name fields['homeaddr_name'].initial = last_name if contents.get('title'): title = vcf_data.title.value position = Position.objects.filter(title__icontains=title).first() if position: fields['position'].initial = position.id else: fields['position'].help_text = self.other_help_text + title init_detail = self._init_detail_field init_detail(contents.get('tel'), self.phone_dict) init_detail(contents.get('email'), self.email_dict) init_detail(contents.get('url'), self.url_dict) def _init_detail_field(self, detail_data, field_dict): if detail_data: fields = self.fields for key in detail_data: param = key.params.get('TYPE') if param: try: fields[field_dict[param[0]]].initial = key.value except KeyError: # eg: invalid type, hidden field pass else: self._generate_help_text(field_dict['HOME'], key.value) def _init_orga_field(self, vcf_data): if vcf_data.contents.get('org'): fields = self.fields org_name = vcf_data.org.value[0] orga = Organisation.objects.filter(name=org_name).first() if orga: fields['organisation'].initial = orga.id fields['create_or_attach_orga'].initial = True fields['work_name'].initial = org_name fields['workaddr_name'].initial = org_name def _init_addresses_fields(self, vcf_data): fields = self.fields get_prefix = self.address_prefixes.get for adr in vcf_data.contents.get('adr', ()): param = adr.params.get('TYPE') value = adr.value if param: prefix = get_prefix(param[0]) if prefix is None: continue box = value.box fields[prefix + 'address'].initial = ( box + ' ' + value.street) if box else value.street fields[prefix + 'city'].initial = value.city fields[prefix + 'country'].initial = value.country fields[prefix + 'code'].initial = value.code fields[prefix + 'region'].initial = value.region else: self._generate_help_text( 'homeaddr_address', ', '.join([ value.box, value.street, value.city, value.region, value.code, value.country, ]), ) def _generate_help_text(self, field_name, value): field = self.fields[field_name] help_text = field.help_text if not help_text: field.help_text = self.type_help_text + value else: field.help_text = '{} | {}'.format(help_text, value) def _clean_orga_field(self, field_name): cleaned_data = self.cleaned_data cleaned = cleaned_data.get(field_name) if cleaned_data['create_or_attach_orga'] and not cleaned: raise ValidationError( self.error_messages['required4orga'], code='required4orga', ) return cleaned clean_work_name = lambda self: self._clean_orga_field('work_name') clean_relation = lambda self: self._clean_orga_field('relation') def _clean_update_checkbox(self, checkbox_name): cleaned_data = self.cleaned_data checked = cleaned_data[checkbox_name] if checked: if not cleaned_data['create_or_attach_orga']: raise ValidationError( self.error_messages['no_orga_creation'], code='no_orga_creation', ) elif not cleaned_data['organisation']: raise ValidationError( self.error_messages['orga_not_selected'], code='orga_not_selected', ) return checked clean_update_work_name = lambda self: self._clean_update_checkbox( 'update_work_name') clean_update_work_phone = lambda self: self._clean_update_checkbox( 'update_work_phone') clean_update_work_email = lambda self: self._clean_update_checkbox( 'update_work_email') clean_update_work_fax = lambda self: self._clean_update_checkbox( 'update_work_fax') clean_update_work_url_site = lambda self: self._clean_update_checkbox( 'update_work_url_site') clean_update_work_address = lambda self: self._clean_update_checkbox( 'update_work_address') def clean_update_field(self, field_name): cleaned_data = self.cleaned_data value = cleaned_data[field_name] if not value and \ all(cleaned_data[k] for k in ('create_or_attach_orga', 'organisation', 'update_' + field_name)): raise ValidationError(self.error_messages['required2update'], code='required2update') return value clean_work_phone = lambda self: self.clean_update_field('work_phone') clean_work_email = lambda self: self.clean_update_field('work_email') clean_work_fax = lambda self: self.clean_update_field('work_fax') clean_work_url_site = lambda self: self.clean_update_field('work_url_site') clean_work_address = lambda self: self.clean_update_field('work_address') def _create_contact(self, cleaned_data): get_data = cleaned_data.get return Contact.objects.create( user=cleaned_data['user'], civility=cleaned_data['civility'], first_name=cleaned_data['first_name'], last_name=cleaned_data['last_name'], position=get_data('position'), # NB: we do not use cleaned_data.get() in order to not overload # default fields values **{ fname: cleaned_data[fname] for fname in self.contact_details if fname in cleaned_data }) def _create_address(self, cleaned_data, owner, data_prefix): # NB: we do not use cleaned_data.get() in order to not overload default fields values kwargs = {} for form_fname, model_fname in self.address_mapping: try: kwargs[model_fname] = cleaned_data[data_prefix + form_fname] except KeyError: pass address = Address(owner=owner, **kwargs) if address: address.save() return address def _create_image(self, contact): cleaned_data = self.cleaned_data image_encoded = cleaned_data['image_encoded'] if image_encoded: img_name = secure_filename('{}_{}_{}'.format( contact.last_name, contact.first_name, contact.id)) img_path = None if image_encoded.startswith(URL_START): tmp_img_path = None try: if int(urlopen(image_encoded).info() ['content-length']) <= settings.VCF_IMAGE_MAX_SIZE: tmp_img_path = path.normpath( path.join(IMG_UPLOAD_PATH, img_name)) urlretrieve( image_encoded, path.normpath( path.join(settings.MEDIA_ROOT, tmp_img_path))) except: logger.exception('Error with image') else: img_path = tmp_img_path else: # TODO: manage urls encoded in base64 ?? try: # TODO: factorise with activesync ?? img_data = base64.decodebytes(image_encoded.encode()) img_path = handle_uploaded_file( ContentFile(img_data), path=IMG_UPLOAD_PATH.split('/'), name='{}.{}'.format( img_name, get_image_format(img_data), ), ) except Exception: logger.exception('VcfImportForm.save()') if img_path: return Document.objects.create( user=cleaned_data['user'], title=gettext('Image of {contact}').format( contact=contact), filedata=img_path, linked_folder=Folder.objects.get(uuid=UUID_FOLDER_IMAGES), description=gettext('Imported by VCFs'), ) def _create_orga(self, contact): cleaned_data = self.cleaned_data if cleaned_data['create_or_attach_orga']: get_data = cleaned_data.get organisation = get_data('organisation') save_orga = False user = cleaned_data['user'] addr_prefix = WORK_ADDR_PREFIX if organisation: # TODO: select_for_update() option in CreatorEntityField ? organisation = Organisation.objects.select_for_update().get( id=organisation.id) for fname in self.orga_fields: if get_data('update_work_' + fname): setattr(organisation, fname, get_data('work_' + fname)) if get_data('update_work_address'): billing_address = organisation.billing_address if billing_address is not None: for form_fname, model_fname in self.address_mapping: value = get_data(addr_prefix + form_fname) if value: setattr(billing_address, model_fname, value) organisation.billing_address.save() else: organisation.billing_address = self._create_address( cleaned_data, owner=organisation, data_prefix=addr_prefix, ) save_orga = True else: # NB: we do not use cleaned_data.get() in order to not overload default fields values orga_kwargs = {} for fname in self.orga_fields: try: orga_kwargs[fname] = cleaned_data['work_' + fname] except KeyError: pass organisation = Organisation.objects.create(user=user, **orga_kwargs) orga_addr = self._create_address( cleaned_data, owner=organisation, data_prefix=addr_prefix, ) if orga_addr is not None: organisation.billing_address = orga_addr save_orga = True if save_orga: organisation.save() Relation.objects.create( user=user, subject_entity=contact, type=cleaned_data['relation'], object_entity=organisation, ) @atomic def save(self, *args, **kwargs): cleaned_data = self.cleaned_data save_contact = False contact = self._create_contact(cleaned_data) image = self._create_image(contact) if image is not None: contact.image = image save_contact = True contact_addr = self._create_address( cleaned_data, owner=contact, data_prefix=HOME_ADDR_PREFIX, ) if contact_addr is not None: contact.billing_address = contact_addr save_contact = True self._create_orga(contact) if save_contact: contact.save() return contact