I have this profile resource:
class ProfileResource(resources.ModelResource):
email = fields.Field(attribute='email',
widget=CharWidget(),
column_name='email')
class Meta:
model = Profile
clean_model_instances = True
import_id_fields = ('email',)
which validates the email when the profile is created. It works fine but when I use it as a foreign key like:
class InvestmentResource(resources.ModelResource):
email = fields.Field(attribute='email',
widget=ForeignKeyWidget(Profile, field='email'),
column_name='email')
class Meta:
model = Investment
clean_model_instances = True
import_id_fields = ('id',)
def before_import_row(self, row, row_number=None, **kwargs):
self.email = row["email"]
self.profile__firstname = row["firstname"]
self.profile__lastname = row["lastname"]
def after_import_instance(self, instance, new, row_number=None, **kwargs):
"""
Create any missing Profile entries prior to importing rows.
"""
try:
profile, created = Profile.objects.get_or_create(email=self.email)
profile.firstname = self.profile__firstname
profile.lastname = self.profile__lastname
profile.save()
instance.profile = profile
except Exception as e:
print(e, file=sys.stderr)
It doesn't validate the email anymore. I tried adding two widgets, ForeignKeyWidget
and CharWidget
for the email on InvestmentResource
, but it didn't work.
How do I then validate the email inside the InvestmentResource
?
The issue is that your 'email' field is being treated as a lookup key and not an email address. This can be resolved by customizing ForeignKeyWidget
to add email validation.
What is happening is that you are importing an investment resource, which might be something like:
email,firstname,lastname
bob@example.com,bob,smith
You have configured InvestmentResource
to use the email as a lookup key for Profile
. This means that django-import-export
is not going to process it as an email address, but instead as lookup key for Profile
, so in the code (ForeignKeyWidget
) it will be doing something like:
Profile.objects.get(email='bob@example.com')
If this is successful, your Investment
instance will now have the correct profile as a foreign key reference.
This will raise a DoesNotExist
exception if the associated Profile
is not present. Therefore you can argue that an email address sent in the csv must be valid, because it if is not then no lookup will be possible.
However, if you want to check that an email is syntactically valid before you attempt to load the Profile
, then this is easy to do, you need to override ForeignKeyWidget
to perform validation first:
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
class ValidatingForeignKeyWidget(widgets.ForeignKeyWidget):
def clean(self, value, row=None, *args, **kwargs):
try:
validate_email(value)
except ValidationError as e:
# a quirk of import-export means that the ValidationError
# should be re-raised
raise ValueError(f"invalid email {e}")
try:
val = super().clean(value)
except self.model.DoesNotExist:
raise ValueError(f"{self.model.__name__} with value={value} does not exist")
return val