Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for extra fields through methods on resource class. #530

Conversation

Projects
None yet
3 participants
@michaelkuty
Copy link

commented Oct 3, 2016

Really small change which adds support for simple extra fields without custom field classes, everything works as classic model attributes.

    class MyModelResource(ModelReource):

        def export_myextra_field(self, obj):

            return obj.related.value

        def save_myextra_field(self, obj, data):
            # optional

            RelatedObject.create(**data)
            RelatedObject1.create(**data)

        def get_myextra_field_name(self):
            # optional

            return "ColumnName"
@shaggyfrog

This comment has been minimized.

Copy link
Contributor

commented Oct 3, 2016

So you're proposing we add three new API functions, to allow a user to add only a single "extra" field to resource?

I don't see how this provides any substantial benefit over using a field attribute, given the added complexity for such a small use case.

Am I missing something?

@michaelkuty

This comment has been minimized.

Copy link
Author

commented Oct 3, 2016

That is the point you can simple write import without limitations, which is usefull especially in a complex imports where you have for example Company which has two addresses(copy driven by checkbox), some User entities with CompanyMemberhips and other entities like Account, ContactInformtion etc.. which could be easily handled by one or more methods depends on what you want which are called in your ordering and all this with diff and transaccions.

I need this features every time when i creating imports/exports stuff and today I decided to make this to the core and I was was surprised how simple is it (new lines ratio).

I respect your opinion about that new API, but without breaking anything and with some lines I proposed usefull extension.

@shaggyfrog

This comment has been minimized.

Copy link
Contributor

commented Oct 4, 2016

Yes, by only adding, you aren't necessarily breaking anything, but that's not sufficient. Adding complexity to an API requires a clear use case. If you can say "without these API changes, X is impossible" or "Y is difficult because it requires even more complex changes A B C", then you might have a case.

But you are saying the only reason you are adding these 3 new API class is because you don't want to add a field attribute to a class. I still don't understand why adding that field is a problem.

I don't want to close this PR quite yet because I want to give you a chance to make a clear case for your changes.

@michaelkuty

This comment has been minimized.

Copy link
Author

commented Oct 4, 2016

Yes, i know that, i need make simple import/export without writing 30 fields with same code. I can just write method instead of writing fields. with this lines we can simple do this

from csb.identity.utils import get_customer
from django.contrib.auth.models import User
from import_export import fields, resources, widgets

from .models import (Company, CompanyAddress, CompanySegment,
                     Customer, UserContact)
from oscar.apps.address.models import Country

COMPANY_FIELDS = ('external_id', 'name', 'comp_id', 'vat_id', 'segment',
                  'line1', 'line4', 'postcode',
                  'email', 'first_name', 'last_name', 'phone_number', 'password',
                  'garant_name', 'garant_email', 'garant_phone_number',
                  'garant_password', 'garant_id')


class CompanyResource(resources.ModelResource):

    external_id = fields.Field(
        'external_id',
        column_name='CASE ID')

    name = fields.Field(
        'name',
        column_name='name')

    comp_id = fields.Field('comp_id',
                           column_name='ICO')

    vat_id = fields.Field('vat_id',
                          column_name='DIC')

    def export_garant_name(self, company):
        if company.garant:
            return company.garant.first_name

    def export_garant_phone_number(self, company):

        if company.garant:
            return company.garant.customer.phone_number

    def export_garant_email(self, obj):

        if obj.garant:
            return obj.garant.email

    def save_garant_name(self, obj, data):

        user, user_created = User.objects.get_or_create(**{
            "username": data["garant_email"],
            "defaults": {
                "email": data["garant_email"],
                "first_name": data["garant_name"],
            }
        })

        mobile_queryset = UserContact.objects.filter(
            user=user,
            type="mobile")
        if mobile_queryset.exists():
            mobile_queryset.update(value=data['garant_phone_number'])
        else:
            UserContact.objects.create(
                user=user,
                value=data['garant_phone_number'],
                type="mobile")

        customer = get_customer(user)

        if user_created:
            user.set_password(data['garant_password'])
            user.save()
            customer.external_id = data['garant_id']
            customer.save()
        obj.garant = user
        obj.save()

    def export_garant_id(self, company):
        if company.garant:
            return company.garant.external_id

    def export_segment(self, company):
        if company.segment:
            return company.segment

    def get_line1_name(self):
        return "Street"

    def get_line4_name(self):
        return "City"

    def export_line1(self, company):
        if company.address:
            return company.address.line1

    def export_line4(self, company):
        if company.address:
            return company.address.line4

    def export_postcode(self, company):
        if company.address:
            return company.address.postcode

    def get_admin(self, company):

        try:
            if company and company.admin:
                return company.admin
        except:
            pass

    def export_email(self, company):

        admin = self.get_admin(company)

        if self.get_admin(company):

            return admin.user.email

    def export_first_name(self, company):

        admin = self.get_admin(company)

        if self.get_admin(company):

            return admin.user.first_name

    def export_last_name(self, company):
        admin = self.get_admin(company)

        if self.get_admin(company):

            return admin.user.last_name

    def export_password(self, company):
        pass

    def export_garant_password(self, company):
        pass

    def export_phone_number(self, company):
        admin = self.get_admin(company)

        if self.get_admin(company):

            return admin.phone_number

    def save_segment(self, obj, data):

        if not obj.segment:

            segment, created = CompanySegment.objects.get_or_create(**{
                'name': data['segment'],
                'defaults': {
                    'verbose_name': data['segment']
                }
            })

            obj.segment = segment
            obj.save()

    def save_line1(self, obj, data):

        if not obj.address:
            address = CompanyAddress.objects.create(**{
                "line1": data[self.get_line1_name()],
                "line4": data[self.get_line4_name()],
                "postcode": data["postcode"],
                "country": Country.objects.get(iso_3166_1_a2="CZ")
            })
            obj.address = address

            billing_address = address
            billing_address.id = None
            billing_address.save()

            obj.billing_address = billing_address
            obj.save()
        else:
            obj.address.line1 = data[self.get_line1_name()]
            obj.address.line4 = data[self.get_line4_name()]
            obj.address.postcode = data["postcode"]
            obj.save()

    def save_email(self, obj, data):
        """
        In this method you can save related objects or
        just save a field
        """
        # print self.fields['email'].clean(data)

        user, user_created = User.objects.get_or_create(**{
            "username": data["email"],
            "defaults": {
                "email": data["email"],
                "first_name": data["first_name"],
                "last_name": data["last_name"]
            }
        })

        if user_created:
            user.set_password(data['password'])
            user.save()

            try:
                get_customer(user)
            except:
                pass

            roles = [Customer.ADMIN,
                     Customer.TECHNICAL_CONTACT,
                     Customer.ADMINISTRATIVE_CONTACT]
            for role in roles:
                obj.add_user(role, customer=user.customer)

            mobile_queryset = UserContact.objects.filter(
                user=user,
                type="mobile")
            if mobile_queryset.exists():
                mobile_queryset.update(value=data['phone_number'])
            else:
                UserContact.objects.create(
                    user=user,
                    value=data['phone_number'],
                    type="mobile")

    class Meta:
        model = Company
        fields = COMPANY_FIELDS
        export_order = COMPANY_FIELDS
        skip_unchanged = False
        import_id_fields = ('comp_id',)
@bmihelac

This comment has been minimized.

Copy link
Member

commented Oct 5, 2016

This looks like something that could be done without changing Resource class, I imagine Field subclass that take delegate saving/exporting to given functions :

email = MyField(func_save=save_email, func_export=export_email, column_name='Email')
@michaelkuty

This comment has been minimized.

Copy link
Author

commented Oct 5, 2016

Yes, your imagination is really nice, but I think that with propagation of resource class to the field we can use method generated by column_name and declared directly on resource class which makes your code more clean or we can use both approaches

michaelkuty added some commits Nov 24, 2016

@stale

This comment has been minimized.

Copy link

commented Feb 11, 2019

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Feb 11, 2019

@stale stale bot closed this Feb 18, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.