1

I'm fairly new to Django, and coding in general so this could be a conceptual oversight, but I'm running out of ideas so any help is appreciated.

I'm trying to add logic to my form's clean() method which has nested try blocks. I'm trying to get object instances from different ForeignKey related models in each try block. The first two levels seem to work fine, but the third level throws the error below. I've printed the value and type for wine_get.wine_id and I get back 6 and 'int' respectively, so I'm not sure why this isn't considered a number.

Environment:


Request Method: POST
Request URL: http://127.0.0.1:8000/post/new/

Django Version: 2.1
Python Version: 3.6.5
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'debug_toolbar',
 'bootstrap4',
 'accounts',
 'groups',
 'posts']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'debug_toolbar.middleware.DebugToolbarMiddleware']



Traceback:

File "/anaconda3/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
  34.             response = get_response(request)

File "/anaconda3/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  126.                 response = self.process_exception_by_middleware(e, request)

File "/anaconda3/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  124.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/anaconda3/lib/python3.6/site-packages/django/views/generic/base.py" in view
  68.             return self.dispatch(request, *args, **kwargs)

File "/anaconda3/lib/python3.6/site-packages/django/contrib/auth/mixins.py" in dispatch
  52.         return super().dispatch(request, *args, **kwargs)

File "/anaconda3/lib/python3.6/site-packages/django/views/generic/base.py" in dispatch
  88.         return handler(request, *args, **kwargs)

File "/anaconda3/lib/python3.6/site-packages/django/views/generic/edit.py" in post
  141.         if form.is_valid():

File "/anaconda3/lib/python3.6/site-packages/django/forms/forms.py" in is_valid
  185.         return self.is_bound and not self.errors

File "/anaconda3/lib/python3.6/site-packages/django/forms/forms.py" in errors
  180.             self.full_clean()

File "/anaconda3/lib/python3.6/site-packages/django/forms/forms.py" in full_clean
  382.         self._clean_form()

File "/anaconda3/lib/python3.6/site-packages/django/forms/forms.py" in _clean_form
  409.             cleaned_data = self.clean()

File "/Users/evan/code/wine/wineProject/wineProject/posts/forms.py" in clean
  78.                                 wine = wine_get,

File "/anaconda3/lib/python3.6/site-packages/django/db/models/manager.py" in manager_method
  82.                 return getattr(self.get_queryset(), name)(*args, **kwargs)

File "/anaconda3/lib/python3.6/site-packages/django/db/models/query.py" in get
  390.         clone = self.filter(*args, **kwargs)

File "/anaconda3/lib/python3.6/site-packages/django/db/models/query.py" in filter
  841.         return self._filter_or_exclude(False, *args, **kwargs)

File "/anaconda3/lib/python3.6/site-packages/django/db/models/query.py" in _filter_or_exclude
  859.             clone.query.add_q(Q(*args, **kwargs))

File "/anaconda3/lib/python3.6/site-packages/django/db/models/sql/query.py" in add_q
  1263.         clause, _ = self._add_q(q_object, self.used_aliases)

File "/anaconda3/lib/python3.6/site-packages/django/db/models/sql/query.py" in _add_q
  1287.                     split_subq=split_subq,

File "/anaconda3/lib/python3.6/site-packages/django/db/models/sql/query.py" in build_filter
  1225.         condition = self.build_lookup(lookups, col, value)

File "/anaconda3/lib/python3.6/site-packages/django/db/models/sql/query.py" in build_lookup
  1096.         lookup = lookup_class(lhs, rhs)

File "/anaconda3/lib/python3.6/site-packages/django/db/models/lookups.py" in __init__
  20.         self.rhs = self.get_prep_lookup()

File "/anaconda3/lib/python3.6/site-packages/django/db/models/fields/related_lookups.py" in get_prep_lookup
  115.                 self.rhs = target_field.get_prep_value(self.rhs)

File "/anaconda3/lib/python3.6/site-packages/django/db/models/fields/__init__.py" in get_prep_value
  965.         return int(value)

Exception Type: TypeError at /post/new/
Exception Value: int() argument must be a string, a bytes-like object or a number, not 'ModelBase'

Local vars from error:

Variable    Value
__class__   <class 'posts.forms.WineForm'>
post_type   'opened'
quantity    2
self    <WineForm bound=True, valid=True, fields=(winemaker;wine;vintage;post_type;quantity;rating;location;tasting_notes)>
vintage '1950'
wine    'Cellar 2'
wine_get    <Wine: Cellar 2>
winemaker   'Cellar 2'
winemaker_get   <WineMaker: Cellar 2>

Session data:

Variable    Value
'_auth_user_backend'  'django.contrib.auth.backends.ModelBackend'
'_auth_user_hash'  '2c72b4192e2d568f616919e66da6b281d3764e4f'
'_auth_user_id'  '2'

models.py

class WineMaker(models.Model):
winemaker_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, unique=True)
region = models.ForeignKey(
    WineRegion,
    db_column = 'region_id',
    related_name = 'winemaker_region',
    on_delete = models.CASCADE,
    null = True
)

def __str__(self):
    return self.name

class Meta:
    db_table = 'winemakers'

class Wine(models.Model):
wine_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=510)
winemaker = models.ForeignKey(
    WineMaker,
    db_column = 'winemaker_id',
    related_name = 'wine_winemakers',
    on_delete = models.CASCADE
)
vintage = models.CharField(
    max_length = 10,
    choices = VINTAGES,
    default = 'none'
)
description = models.TextField(max_length=3000, null=True)
style = models.ForeignKey(
    Style,
    db_column = 'style_id',
    related_name = 'wine_styles',
    on_delete = models.CASCADE,
    null = True
)
varietal = models.ForeignKey(
    Varietal,
    db_column = 'varietal_id',
    related_name = 'wine_varietals',
    on_delete = models.CASCADE,
    null = True
)
blend = models.ForeignKey(
    Blend,
    db_column = 'blend_id',
    related_name = 'wine_blends',
    on_delete = models.CASCADE,
    null = True
)

def __str__(self):
    return self.name

class Meta:
    db_table = 'wines'
    unique_together = ('winemaker', 'name', 'vintage')

class UserCellar(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(
    settings.AUTH_USER_MODEL,
    related_name = 'userCellar_users',
    on_delete = models.CASCADE
)
wine = models.ForeignKey(
    Wine,
    db_column = 'wine_id',
    related_name = 'userCellar_wines',
    on_delete = models.CASCADE
)
quantity = models.SmallIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __int__(self):
    return self.wine_id

def __str__(self):
    return self.wine.name

class Meta:
    db_table = 'user_cellars'
    unique_together = ('user', 'wine')

...

forms.py

from django import forms
from .models import *
from django.contrib.auth import get_user_model
from .choices import VINTAGES, POST_TYPE_CHOICES

user = get_user_model()

class WineForm(forms.Form):
    winemaker = forms.CharField(max_length=255)
    wine = forms.CharField(max_length=510)
    vintage = forms.ChoiceField(
        choices = VINTAGES
    )
    post_type = forms.ChoiceField(
        choices = POST_TYPE_CHOICES
    )
    quantity = forms.IntegerField(
        required = False
    )
    rating = forms.IntegerField(
        required = False
        )
    location = forms.CharField(
        max_length = 255,
        required = False
        )
    tasting_notes = forms.CharField(
        max_length = 2000,
        required = False,
        widget = forms.Textarea
        )

    class Meta:
        labels = {
            'winemaker' : 'Winemaker',
            'wine' : 'Wine',
            'vintage' : 'Vintage',
            'post_type' : 'Activity Type',
            'rating' : 'Rating',
            'location' : 'Location',
            'tasting_notes' : 'Tasting Notes',
        }

    def __init__(self, *args, **kwargs):
        super(WineForm, self).__init__(*args, **kwargs)
        self.fields['quantity'].widget.attrs={
            'id': 'quantity'
        }

    def clean(self):
        super().clean()
        winemaker = self.cleaned_data.get('winemaker')
        wine = self.cleaned_data.get('wine')
        vintage = self.cleaned_data.get('vintage')
        post_type = self.cleaned_data.get('post_type')
        quantity = self.cleaned_data.get('quantity')

        if post_type and quantity:
            # If both fields are valid
            if post_type == 'opened':
                try:
                    # Try to get the WineMaker instance
                    winemaker_get = WineMaker.objects.get(
                        name = winemaker,
                    )
                    try:
                        #Try to get the Wine instance
                        wine_get = Wine.objects.get(
                            name = wine,
                            winemaker = winemaker_get,
                            vintage = vintage,
                        )
                        try:
                            #Try to get the UserCellar instance
                            cellar_get = UserCellar.objects.get(
                                user = user,
                                wine = wine_get,
                            )

    ...
ecdnj
  • 21
  • 6
  • where's the clean method? – dan-klasson Mar 04 '19 at 15:29
  • you should to add get attribute `.id` always where you do the get object. – Brown Bear Mar 04 '19 at 16:06
  • 1
    Don't call your ForeignKeys `_id` (e.g. `region_id`). They are not ids, they are proper references to objects, when you access them in python. In fact, Django automatically adds a field `_id` for you to retrieve the id. Right now, the way you defined it, if `winemaker` is an instance of type `WineMaker`, `winemaker.region_id` gives you a `Region` object (not an id) and `winemaker.region_id_id` gives you the `id` of the `Region` instance. Not ideal. – dirkgroten Mar 04 '19 at 16:55

1 Answers1

1

I'm afraid my original answer below is rubbish. When filtering on a foreign key, Django doesn't care if you pass an object or just a primary key:

# wm is a WineMaker object; the next 2 lines are both valid
wines1 = Wine.objects.filter(winemaker=wm)
wines2 = Wine.objects.filter(winemaker=wm.id)

===================

The stack trace is clear that the issue is with this line (though the error message is arguably less than helpful):

wine_id = wine_get.wine_id

Since wine_id is a foreign key, you need to pass a Wine instance:

wine_id = wine_get

Endre Both
  • 5,540
  • 1
  • 26
  • 31
  • I'd originally set it up this way to that I can more explicitly control DB field naming conventions when viewing the tables via a DB client. Point taken on maintainability. Regarding the change to referencing the object instance, not just the wine_get.wine_id attribute, I tried this and I get the same resulting error message. Since I'm using the same syntax for the wine_get as the nested cellar_get, and only cellar_get is the throwing the error, is it possible that the issue is due to the cellar_get being nested 3 levels deep? The reference to ModelBase is throwing me off as well. – ecdnj Mar 05 '19 at 14:30
  • Updated models and form above. – ecdnj Mar 05 '19 at 15:08
  • `id` is as clear and simple in the DB as in Python code. Where you can gain some clarity at the DB level is with manual table names, where Django prepends the app name by default. I'm going to take another look at the code regarding the actual error. – Endre Both Mar 05 '19 at 15:10
  • I'm stumped. Your listed local vars don't include `user`; is it a valid user instance? – Endre Both Mar 05 '19 at 16:20
  • Yes, running debug tools shows the valid user session. I added them above. Thanks for your feedback. I'll keep digging. – ecdnj Mar 06 '19 at 14:38
  • I'm not sure the session data posted are conclusive evidence that `user` is a valid `User` instance. I'd make sure to see in the debug tool or in a printed statement that `user` is actually a `User` with the expected `user.username`. But I may well be barking up the wrong tree again. Other than that, I'd pare down the clean method to the bare essentials of selecting a `UserCellar` by filtering on a user and a wine, first both hardcoded, then received via the form. – Endre Both Mar 06 '19 at 15:20
  • After paring down the clean method, I was able to determine it was in fact the `User` instance that was the issue. I was able to then use suggestions from [here](https://stackoverflow.com/questions/1057252/how-do-i-access-the-request-object-or-any-other-variable-in-a-forms-clean-met/1057640) to pass the request into the `clean()` method. – ecdnj Mar 07 '19 at 15:37
  • For reference, updated changes were: Updated forms.py __init__ method. `def get_form_kwargs(self): kw = super(NewPost, self).get_form_kwargs() kw['request'] = self.request return kw` Added get_form_kwargs method to NewPost view: `def get_form_kwargs(self): kw = super(NewPost, self).get_form_kwargs() kw['request'] = self.request return kw` – ecdnj Mar 07 '19 at 15:39
  • Good job. It goes to show that error messages in Django (and software in general) still have a ways to go. I had overlooked the line `user = get_user_model()` that should have tipped me off earlier. It definitely helps to keep the code sample as simple as possible while still demonstrating the problem. – Endre Both Mar 07 '19 at 16:05