0

I've got 2 models :

class Fund(LabsManagerBudgetMixin, ActiveDateMixin): 
    ref= models.CharField(max_length=30, blank=True, verbose_name=_('Reference'))
    start_date=models.DateField(null=True, blank=True, verbose_name=_('Start Date'))
    end_date=models.DateField(null=True, blank=True, verbose_name=_('End Date'))
    
class Fund_Item(LabsManagerBudgetMixin, CachedModelMixin):
    amount=models.DecimalField(max_digits=12, decimal_places=2, verbose_name=_('Amount'), default=0, null=True)
    type=models.ForeignKey(Cost_Type, on_delete=models.CASCADE, verbose_name=_('Type'))
    fund=models.ForeignKey(Fund, on_delete=models.CASCADE, verbose_name=_('Related Fund'))

the mixins add some other fields, methods, managers ....

For the second one (Fund_Item) the admin extends "ImportExportModelAdmin" and the ressource class (see below) identify the foreignkey in the before_import_row method from a ref field.

The csv from which I update FundItem objects contains the 'ref', 'start_date', 'end'date' of the foreingkey linked object Fund.

I would like to update alongside the Fund_Item object the value dates of the foreign objects Fund.

I would point it as a value to update if needed at the file upload and parsing, and update the value on the import finalisation.

The doc states: "…using a ForeignKeyWidget has the advantage that it can not only be used for exporting, but also importing data with foreign key relationships."

But I'm only able to update the foreingkey of the current model and not the foreign model attributes.

I don't know where to start digging which method to override. Could point me out a direction?

class FundItemAdminResource(labResource, SkipErrorRessource):
    fund=FundField(
        column_name=_('Ref'),
        attribute='fund', 
        widget=widgets.ForeignKeyWidget(Fund, 'ref'), 
        readonly=False
    )
    type=Field(
        column_name=_('type'),
        attribute='type', 
        widget=widgets.ForeignKeyWidget(Cost_Type, 'short_name'), 
        readonly=False
    )

    start_date=FundField(
        column_name=_('Start Date'),
        attribute='fund', 
        widget=widgets.ForeignKeyWidget(Fund, 'start_date'), 
        readonly=False
    )
    end_date=FundField(
        column_name=_('End Date'),
        attribute='fund', 
        widget=widgets.ForeignKeyWidget(Fund, 'end_date'),
        readonly=False
    )
    
    amount=FundItemField(
        column_name=_('Budget'),
        attribute='amount', 
        widget=widgets.DecimalWidget(),
        readonly=False
    )
            
        
    def before_import_row(self, row, row_number=None, **kwargs):
        query = Q(amount__gte=-1)

        refI = row.get('Ref', None)
        if refI is not None:
            query = query & Q(fund__ref=refI)
            
        typeC = row.get('type', None)
        if typeC is not None:
            query = query & Q(type__short_name=typeC)            
        
        fu = Fund_Item.objects.filter(query)
        if fu is not None and fu.count()==1:
            row["id"] = fu.first().pk
        else:
            row["id"] = None
        return fu
        
    class Meta:
        """Metaclass"""
        model = Fund_Item
        skip_unchanged = True
        clean_model_instances = False
        exclude = [ '' ]
        export_order=[
                      'start_date',
                      'end_date',
                      'type',
                      'fund',
                      'amount',
                      ]

I've tryeid to tweak clean and save method of a custom field, but I land on issues or error each time

class FundField(Field):
     def clean(self, data, **kwargs):
        query = Q(amount__gte=-1)

        refI = data.get('Ref', None)
        if refI is not None:
            query = query & Q(ref=refI)
        
        fu = Fund.objects.filter(query).first()
        return fu

class FundDateField(FundField):
    def clean(self, data, **kwargs):        
        fu = super(FundField, self).clean(data, **kwargs)
        return getattr(fu, self.widget.field, None)
    
    def save(self, obj, data, is_m2m=False, **kwargs):
        # self.attribute=self.attribute+"__"+self.widget.field
        super(FundField, self).save(obj, data, is_m2m, **kwargs)

for the resource field defined as :

start_date=FundDateField(
        column_name=_('Start Date'),
        attribute='fund', 
        widget=widgets.ForeignKeyWidget(Fund, 'start_date'), 
        readonly=False
    )

[Edit]

I've use the clues from matthew below, I've implemented a custom Field which handle the column check and save from the clean method

class FundDateField(FundField):
    def clean(self, data, **kwargs):   
        fu = super().clean(data, **kwargs)
        field = self.widget.field
        attr =getattr(fu, field, None)
        nAttr=data.get(self.column_name)
        if attr!=nAttr:
            setattr(fu, field, nAttr)
            fu.save()
        print(" - fu : "+str(fu))
        
        return fu

and apply it to the resource date fields

start_date=FundDateField(
        column_name=_('Start Date'),
        attribute='fund', 
        widget=widgets.ForeignKeyWidget(Fund, 'start_date'), 
        readonly=False
    )
    end_date=FundDateField(
        column_name=_('End Date'),
        attribute='fund', 
        widget=widgets.ForeignKeyWidget(Fund, 'end_date'),
        readonly=False
    )

1 Answer 1

0

I would like to update alongside the Fund_Item object the value dates of the foreign objects Fund.

It sounds like this is the core issue. You would like to update the Fund instance with fields from the import. The Fund has FK relationship to Fund_Item (which is the base model being imported).

The simplest way to achieve this would be to create a custom ForeignKeyWidget and add logic to the clean() method.

class FundForeignKeyWidget(ForeignKeyWidget):

    def clean(self, value, row=None, **kwargs):
        # this will return the Fund instance (if it exists)
        # fund.ref will be used as the lookup field
        # (exception will be thrown if not exists)
        fund = super().clean(value)

        # add your own error checking here as required
        start_date = parse_datetime(row["Start Date"])
        fund.start_date = start_date

        end_date = parse_datetime(row["End Date"])
        fund.end_date = end_date

        fund.save()

        return fund

Then you don't need to declare 'Start Date', 'End Date' as separate fields, you can create one field for the FK reference:

class FundItemAdminResource(labResource, SkipErrorRessource):
    # This defines that 'ref' will be used as the field to lookup Fund
    fund=FundField(
        column_name=_('Ref'),
        attribute='fund', 
        widget=FundForeignKeyWidget(Fund, 'ref'), 
    )

You could also use before_import_row() or after_import_row():

    def before_import_row(self, row, **kwargs):
        fund = Fund.objects.get(ref=row["ref")
        fund.start_date = parse_datetime(row["Start Date"])
        fund.end_date = parse_datetime(row["End Date"])
        fund.save()

Also, I don't know what's going on in your before_import_row() method. It looks like you're trying to load a Fund_Item based on values in the import. Don't forget that import-export will handle this for you. You need to declare the lookup rows in import_id_fields (docs). So it might look something like:

    import_id_fields = ('fund', 'type')

It is well worth setting a debugger and stepping through so you can see exactly what is going on. This will save a lot of time in the long run.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks Matthew, it puts me on the track. Actually, the clean method of th widget is not called at all during the process. But the clean method of field is! edit Don't know how to add a more detail answer in stackoverflow! should I edit the question?
best to use the debugger to figure out what's going on if you can
You mean smthing like pdb? Currently I manage the debug with the logger/print but it's rather fastidious
you can download and use a free version of PyCharm
thks, Actually I'm using vscode, and I dev from a debian VM (without GUI) dedicated to django; and i use several computer for coding, and i can't install python + package +venv in every machine (+git+docker+....). I suppose pyCharm (that I've never used) debug the same way? i would love some fancy remote debugger btw.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.