Django: Assigning ForeignKey - Unable to get repr for class

PANDA Stack picture PANDA Stack · Jul 23, 2015 · Viewed 16.5k times · Source

I ask this question here because, in my searches, this error has been generally related to queries rather than ForeignKey assignment.

The error I am getting occurs in a method of a model. Here is the code:

class Deal(models.Model):

    ...model_fields...

    def _update_existing_deal(self, deal_dict):
        #deal made from deal_dict here, psuedo code below
        deal = Deal(deal_dict)
        HistoricalDeal().create_historical_deal(deal)


        self.price = deal_dict.get('price', self.price)
        if self.comment != deal_dict.get['comment']:
            self.comment = deal_dict.get('comment', '')
        self.set_unit_price()
        logger.debug(
            'Existing deal, (pk: %d), updated.',
            self.pk
        )

class HistoricalDeal(models.Model):
    deal = models.ForeignKey(Deal)
    created_at = models.DateTimeField(auto_now_add=True)
    price = models.DecimalField(max_digits=8, decimal_places=2, blank=True,
                                default=0)
    unit_price = models.DecimalField(decimal_places=2, max_digits=6,
                                     null=True, blank=True)

    def create_historical_deal(self, deal):
        self.deal = deal
        self.price = deal.price
        self.unit_price = deal.unit_price
        self.save()
        logger.debug(
            'HistoricalDeal created for Deal with pk: %d.',
            deal.pk
        )

    def __str__(self):
        return ', '.join([self.deal.name, self.created_at.date()])

The thing is, the Deal I am passing to HistoricalDeal.create_historical_deal is legit. Here's a picture of the debugger in PyCharm. Debugger Message

For search engines, the message there is:

Unable to get repr for <class 'deals.models.HistoricalDeal'>

Any ideas?

Edit: Full code for Deal below:

class Deal(models.Model):
    LUMBER = 'lumber'
    WOODBLANK = 'woodblank'
    DOWEL = 'dowel'
    VENEER = 'veneer'
    PLYWOOD = 'plywood'

    TYPE_CHOICES = (
        (LUMBER, 'Lumber'),
        (WOODBLANK, 'Wood Blank'),
        (DOWEL, 'Dowel'),
        (VENEER, 'Veneer'),
        (PLYWOOD, 'Plywood'),
    )

    # define the correct method and unit for each material type
    # mainly used in `get_unit_price`
    MATERIAL_MAPPING = {
        LUMBER: {
            'method': lambda self: float(self.price) / (float(self.board_feet) or 1),
            'unit': 'BF',
        },
        WOODBLANK: {
            'method': lambda self: self.price,
            'unit': 'Purchase',
        },
        DOWEL: {
            'method': lambda self: float(self.price) / (float(self.linear_feet) or 1),
            'unit': 'LF',
        },
        VENEER: {
            'method': lambda self: float(self.price) / (float(self.square_feet) or 1),
            'unit': 'SF',
        },
        PLYWOOD: {
            'method': lambda self: float(self.price) / (float(self.square_feet) or 1),
            'unit': 'SF',
        }
    }

    name = models.CharField(max_length=200)
    slug = models.SlugField(max_length=100)
    url = models.CharField(max_length=200, blank=True)
    vendor = models.ForeignKey('Vendor')
    category = models.ForeignKey('Category')
    active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    price = models.DecimalField(max_digits=8, decimal_places=2, blank=True,
                                default=0)
    comment = models.TextField(blank=True)
    img = models.ImageField(blank=True)
    unique_lot = models.IntegerField(default=None, blank=True, null=True)

    material_type = models.CharField(max_length=9, choices=TYPE_CHOICES)

    # attributes specific to material types
    board_feet = models.DecimalField(decimal_places=2, max_digits=6,
                                     null=True, blank=True)
    thickness = models.CharField(max_length=15,
                                 null=True, blank=True)
    length = models.CharField(max_length=15,
                              null=True, blank=True)
    width = models.CharField(max_length=15,
                             null=True, blank=True)
    diameter = models.CharField(max_length=15,
                                null=True, blank=True)
    linear_feet = models.DecimalField(decimal_places=2, max_digits=6,
                                      null=True, blank=True)
    square_feet = models.DecimalField(decimal_places=2, max_digits=6,
                                      null=True, blank=True)
    adhesive_backing = models.NullBooleanField(default=False,
                                               null=True, blank=True)

    image = models.ForeignKey('Image', null=True, blank=True)

    unit_price = models.DecimalField(decimal_places=2, max_digits=6,
                                     null=True, blank=True)

    def set_unit_price(self):
        method = self.MATERIAL_MAPPING[self.material_type]['method']
        self.unit_price = method(self)
        self.save()

    @property
    def get_unit_price(self):
        method = self.MATERIAL_MAPPING[self.material_type]['method']
        unit = self.MATERIAL_MAPPING[self.material_type]['unit']
        return {
            'value': method(self),
            'units': unit
        }

    @classmethod
    def _find_matching_deal(cls, deal_dict):
        """ Check for an existing deal that matches `deal_dict` """
        # TODO: use get_or_create?
        match = cls.objects.filter(
            material_type=deal_dict.get('deal_type', None),
            board_feet=deal_dict.get('boardfeet', None),
            thickness=deal_dict.get('thickness', None),
            length=deal_dict.get('length', None),
            width=deal_dict.get('width', None),
            diameter=deal_dict.get('diameter', None),
            linear_feet=deal_dict.get('linear_feet', None),
            square_feet=deal_dict.get('square_feet', None),
            adhesive_backing=deal_dict.get('adhesive_backing', None),
            unique_lot=deal_dict.get('unique_lot', None),
            category=deal_dict['category'],
            url=deal_dict['url']
        )
        if not match:
            return None
        # Because of the unique constraint, there should only be one match
        assert len(match) == 1
        return match[0]

    @staticmethod
    def _guess_category(name, url):
        """ Find the category that best matches the deal name/url """
        name = name.lower()
        url = url.lower()
        # create a string of unique possible name variants
        search_string = '|'.join({
            name,
            name.replace(' ', ''),
            name.replace('_', ' '),
            name.replace('-', ' '),
            name.replace('_', ''),
            name.replace('-', ''),
            url.replace(' ', ''),
            url.replace('-', ' '),
            url.replace('_', ' '),
            url.replace('-', ''),
            url.replace('_', ''),
        })
        # TODO: cache categories, don't query each time
        all_categories = Category.objects.all()

        # get a list of categories that might match
        matching_categories = [
            category for category in all_categories
            if category.name.lower() in search_string
            ]
        logger.debug('Found these category matches for %s: %s', name,
                     matching_categories)
        if len(matching_categories) == 0:
            matching_categories = [
                category for category in all_categories
                if category.name.replace(' ', '').lower() in search_string
                ]
        if len(matching_categories) == 0:
            # add it to the Misc category
            return Category.objects.get_or_create(
                name="Miscellaneous",
                defaults={'slug': 'misc'}
            )[0]
        # return the first match
        return matching_categories[0]

    @staticmethod
    def _get_vendor(vendor_name):
        return Vendor.objects.get_or_create(
            name=vendor_name,
            defaults={'shipping': False}
        )[0]

    @staticmethod
    def _capitalize_name(name):
        return name.replace('-', ' ').replace('_', ' ').title()

    def _update_existing_deal(self, deal_dict):
        self.price = deal_dict.get('price', self.price)
        if self.comment != deal_dict.get['comment']:
            self.comment = deal_dict.get('comment', '')
        self.set_unit_price()
        logger.debug(
            'Existing deal, (pk: %d), updated.',
            self.pk
        )

    @classmethod
    def save_from_dict(cls, deal_dict):
        logger.debug('saving deal from dict: %s', deal_dict)
        deal_dict['category'] = cls._guess_category(deal_dict['name'], deal_dict['url'])
        deal_dict['name'] = cls._capitalize_name(deal_dict['name'])
        existing_deal = cls._find_matching_deal(deal_dict)
        if not existing_deal:
            logger.debug('This is a new deal, saving it')
            current_deal = cls.objects.create(
                name=deal_dict.get('name'),
                slug=deal_dict.get('slug', ''),
                url=deal_dict.get('url'),
                image=Image.from_url(deal_dict.get('image_url', None)),
                price=deal_dict.get('price'),
                comment=''.join(deal_dict.get('comment', [])),
                material_type=deal_dict.get('deal_type', None),
                board_feet=deal_dict.get('boardfeet', None),
                thickness=deal_dict.get('thickness', None),
                length=deal_dict.get('length', None),
                width=deal_dict.get('width', None),
                diameter=deal_dict.get('diameter', None),
                linear_feet=deal_dict.get('linear_feet', None),
                square_feet=deal_dict.get('square_feet', None),
                adhesive_backing=deal_dict.get('adhesive_backing', None),
                unique_lot=deal_dict.get('unique_lot', None),
                category=deal_dict['category'],
                vendor=cls._get_vendor(deal_dict['vendor_name']),
            )
            current_deal.set_unit_price()
        else:
            logger.debug(
                'Existing deal, updating it (pk: %d)',
                existing_deal.pk
            )
            HistoricalDeal().create_historical_deal(existing_deal)
            existing_deal._update_existing_deal(deal_dict)

    def __str__(self):
        return '<Deal: %d, %s>' % (self.pk, self.name)

    class Meta(object):
        unique_together = ((
                               'material_type',
                               'board_feet',
                               'thickness',
                               'length',
                               'width',
                               'diameter',
                               'linear_feet',
                               'square_feet',
                               'adhesive_backing',
                               'unique_lot',
                               'category',
                               'url'
                           ),)

Answer

PANDA Stack picture PANDA Stack · Jul 25, 2015

The solution was pointed out by James Bennet's comment on the answer from Sandwich Heat:

In the HistoricalDeal model's __str__ method, I passed a date object to ''.join([...]) (which is not a string). Simply coercing that value into a string with str() eliminated the problem.