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.
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'
),)
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.