title: Django: Assigning ForeignKey - Unable to get repr for class
body: <p>I ask this question here because, in my searches, this error has been generally related to queries rather than ForeignKey assignment.</p>
  
  <p>The error I am getting occurs in a method of a model. Here is the code:</p>
  
  <pre><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()])
  </code></pre>
  
  <p>The thing is, the <code>Deal</code> I am passing to <code>HistoricalDeal.create_historical_deal</code> is legit. Here's a picture of the debugger in PyCharm.
  <a href="https://i.stack.imgur.com/ErTP6.png" rel="noreferrer"><img src="https://i.stack.imgur.com/ErTP6.png" alt="Debugger Message"></a></p>
  
  <p>For search engines, the message there is:</p>
  
  <pre><code>Unable to get repr for &lt;class 'deals.models.HistoricalDeal'&gt;
  </code></pre>
  
  <p>Any ideas?</p>
  
  <p>Edit: Full code for Deal below:</p>
  
  <pre><code>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 '&lt;Deal: %d, %s&gt;' % (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'
                             ),)
  </code></pre>