social django 'NoneType' 对象没有属性 'city_set'

social django 'NoneType' object has no attribute 'city_set'

如果我使用 Facebook 帐户登录,当我尝试更新我的用户配置文件时出现错误('NoneType' 对象没有属性 'city_set')。我假设问题是由于:一开始没有选择国家和城市。正常注册和更新用户资料没有问题,但 social_django 违反规则。我正在使用 abstractbaseuser 模型,并且有一个国家/地区城市模型。我尝试了很多方法来弄清楚,但这些都没有帮助。非常感谢您提前抽出时间...

settings.py

"""
Django settings for project_folder project.

Generated by 'django-admin startproject' using Django 3.0.6.

For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '7aa*ng4p*o!9h4%hyfgu=9xy69aumg6hzbz3g)1mf^4!+gi+e0'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # apps
    'apps.daily_brief',
    'apps.users',
    'apps.crm',

    # side-apps
    'crispy_forms',
    'django_cleanup',
    'phonenumber_field',
    'social_django',
    'verify_email', 
]

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

    #social_django
    'social_django.middleware.SocialAuthExceptionMiddleware',
]

#social_django
AUTHENTICATION_BACKENDS = (
    'social_core.backends.github.GithubOAuth2',
    'social_core.backends.twitter.TwitterOAuth',
    'social_core.backends.facebook.FacebookOAuth2',
    'django.contrib.auth.backends.ModelBackend',
)

#social_django
SOCIAL_AUTH_FACEBOOK_KEY = '******************'  # App ID
SOCIAL_AUTH_FACEBOOK_SECRET = '***********************'  # App Secret
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
    'fields': 'id,name,email, gender, birthday, link, location, hometown, first_name, last_name', 
}

#social_django
# SOCIAL_AUTH_PIPELINE = (
    # 'social_core.pipeline.social_auth.social_details',
    # 'social_core.pipeline.social_auth.social_uid',
    # 'social_core.pipeline.social_auth.auth_allowed',
    # 'social_core.pipeline.social_auth.social_user',
    # 'social_core.pipeline.user.get_username',
    # 'social.pipeline.social_auth.associate_by_email',
    # 'social.pipeline.user.create_user',
    # 'social_core.pipeline.social_auth.associate_user',
    # 'social_core.pipeline.social_auth.load_extra_data',
    # 'social_core.pipeline.user.user_details',
    # 'social_core.pipeline.debug.debug',
    # 'apps.users.pipeline.save_account', # create a model in users and add here
# )

ROOT_URLCONF = 'project_folder.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR + '/templates/',],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

AUTH_USER_MODEL = 'users.Account'

WSGI_APPLICATION = 'project_folder.wsgi.application'



# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/

STATIC_URL = '/static/'
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),)
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

try:
    from project_folder.local_settings import *
except ImportError:
    print('local_settings error')
    pass

CRISPY_TEMPLATE_PACK = 'bootstrap4'

LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'daily_brief_home'
LOGOUT_URL = 'logout'

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ['DB_USER'] 
EMAIL_HOST_PASSWORD = os.environ['DB_PASS']  

models.py

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager  
from PIL import Image  
from django.db.models.signals import post_save  
from django.dispatch import receiver 
from django.utils import timezone  
from phonenumber_field.modelfields import PhoneNumberField

from django.utils.text import slugify 
from .utils import unique_slug_generator_account
from django.db.models.signals import pre_save

from django.urls import reverse



class Country(models.Model):
    name = models.CharField(max_length=40)

    def __str__(self):
        return self.name


class City(models.Model):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)
    name = models.CharField(max_length=40)

    def __str__(self):
        return self.name


class AccountManager(BaseUserManager):
    def create_user(self, email, username, password=None): # username field and required fields in Account database must be here
        if not email:
            raise ValueError("Users must have an email adress")
        if not username:
            raise ValueError("Users must have an username")

        user = self.model( #if email and username condition passes in the top, than we can create the account
                email=self.normalize_email(email), # normalize convert the characters to lowercase
                username=username,
            )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, username, password):
        user = self.create_user( #if email and username condition passes in the top, than we can create the account
                email=self.normalize_email(email), # normalize convert the characters to lowercase
                password=password,
                username=username,
            )
        user.is_admin=True
        user.is_staff=True
        user.is_superuser=True
        user.save(using=self._db)
        return user


class Account(AbstractBaseUser):  
    email               = models.EmailField(max_length=60, unique=True)
    username            = models.CharField(max_length=30, unique=True)

    date_joined         = models.DateTimeField(verbose_name='date joined', auto_now_add=True)
    last_login          = models.DateTimeField(verbose_name='last_login', auto_now=True)
    is_admin            = models.BooleanField(default=False)
    is_active           = models.BooleanField(default=True)
    is_staff            = models.BooleanField(default=False)
    is_superuser        = models.BooleanField(default=False)

    company             = models.CharField(default='', max_length=200, help_text='In order to share the application with other users; you need to provide a valid company information', blank=True, null=True)
    phone               = PhoneNumberField('Phone Number', default='', help_text='Kindly use the global formatting without spaces (+90 531 531 53 53 = +905315315353)', unique=True, blank=True, null=True)
    first_name          = models.CharField(max_length=100, blank=True, null=True)
    last_name           = models.CharField(max_length=100, blank=True, null=True)
    GENDER_LIST=(
        ('Male', 'Male'),
        ('Female', 'Female')
        )
    gender              = models.CharField(max_length=100, choices=GENDER_LIST, blank=True, null=True) 
    birthday            = models.DateField(help_text='Kindly provide a global date formatting as follows: (YYYY-MM-DD)', auto_now=False, blank=True, null=True)
    country             = models.ForeignKey(Country, on_delete=models.SET_NULL,  blank=True, null=True)
    city                = models.ForeignKey(City, on_delete=models.SET_NULL,  blank=True, null=True)
    district            = models.CharField(max_length=100, blank=True, null=True)
    image               = models.ImageField(default='default.jpg', upload_to='profile_pics')
    slug                = models.SlugField(max_length=200, blank=True, null=True) 

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username'] #username and password are required by default

    objects = AccountManager() # tells this Account database that; the manager database which is in the top AccountManager

    def __str__(self):  
        return f'{self.email}'  

    def has_perm(self, perm, obj=None): # has permissions; if user is an admin; he can change stuff in the database 
        return self.is_admin

    def has_module_perms(self, app_label): # has module permissions;  
        return True
        # return self.is_admin

    def get_absolute_url(self):
        return reverse('account_detailview', kwargs={'pk': self.pk})


def pre_save_receiver(sender, instance, *args, **kwargs): 
    if instance.slug:
        instance.slug = slugify(instance.username)
    else:
        instance.slug = slugify(instance.username)

pre_save.connect(pre_save_receiver, sender = Account) 

forms.py

from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import Account, City
from django.shortcuts import render, redirect

class AccountForm(UserCreationForm, forms.ModelForm):
    class Meta:
        model = Account
        # fields = '__all__'
        fields = ('email','username','password1','password2','company','phone','first_name','last_name','gender','birthday','country','city','district','image')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['city'].queryset = City.objects.none()

        if 'country' in self.data:
            try:
                country_id = int(self.data.get('country'))
                self.fields['city'].queryset = City.objects.filter(country_id=country_id).order_by('name')
            except (ValueError, TypeError):
                pass  # invalid input from the client; ignore and fallback to empty City queryset
        elif self.instance.pk:
            self.fields['city'].queryset = self.instance.country.city_set.order_by('name')


class AccountUpdateForm(forms.ModelForm):
    class Meta:
        model = Account
        # fields = '__all__'
        fields = ('email','username','company','phone','first_name','last_name','gender','birthday','country','city','district','image')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['city'].queryset = City.objects.none()

        if 'country' in self.data:
            try:
                country_id = int(self.data.get('country'))
                self.fields['city'].queryset = City.objects.filter(country_id=country_id).order_by('name')
            except (ValueError, TypeError):
                pass  # invalid input from the client; ignore and fallback to empty City queryset
        elif self.instance.pk:
            self.fields['city'].queryset = self.instance.country.city_set.order_by('name')

views.py

from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth import login, authenticate
from .forms import AccountForm, AccountUpdateForm
from django.views.generic import TemplateView, ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.decorators import login_required
from .models import Account, City
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin, PermissionRequiredMixin, AccessMixin


class AccountListView(LoginRequiredMixin, ListView):
    model = Account
    context_object_name = 'accounts'


class AccountDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView):
    model = Account

    def test_func(self):  
        account = self.get_object()  
        if self.request.user.email == account.email:
            return True  
        return False


class AccountCreateView(CreateView):
    model = Account
    form_class = AccountForm
    # success_url = reverse_lazy('account_listview')


class AccountUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Account
    form_class = AccountUpdateForm
    # fields = ('email','username', 'company','phone','first_name','last_name','gender','birthday','country','city','district','image')
    # success_url = reverse_lazy('account_detailview')

    def form_valid(self, form): 
        form.instance.user = self.request.user
        return super().form_valid(form)

    def test_func(self):  
        account = self.get_object()  
        if self.request.user.email == account.email:
            return True  
        return False


class AccountDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = Account
    success_url = '/'

    def test_func(self):  
        account = self.get_object()  
        if self.request.user.email == account.email:
            return True  
        return False


def load_cities(request):
    country_id = request.GET.get('country')
    cities = City.objects.filter(country_id=country_id).order_by('name')
    return render(request, 'users/city_dropdown_list_options.html', {'cities': cities})


# class SocialMediaLoginView(TemplateView):
#     template_name = 'users/login.html'


def resend_email_verification(request):
    pass

urls.py

from django.urls import path, include
from .views import AccountListView, AccountDetailView, AccountCreateView, AccountUpdateView, AccountDeleteView, resend_email_verification, load_cities
from django.contrib.auth import views as auth_views
from django.conf import settings  
from django.conf.urls.static import static  
from django.views.static import serve  
from .decorators import staff_member_required # from django.contrib.admin.views.decorators import staff_member_required

from django.conf.urls import url, include

urlpatterns = [
    path('accounts/', staff_member_required(AccountListView.as_view()), name='account_listview'),
    path('accounts/register/', AccountCreateView.as_view(), name='account_createview'),
    path('accounts/<int:pk>/', AccountDetailView.as_view(), name='account_detailview'),
    path('accounts/<int:pk>/update/', AccountUpdateView.as_view(), name='account_updateview'),
    path('accounts/<int:pk>/delete/', AccountDeleteView.as_view(), name='account_deleteview'),

    path('ajax/load-cities/', load_cities, name='ajax_load_cities'),

    url(r'^oauth/', include('social_django.urls', namespace='social')), #social_django

    path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'),
    path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name='logout'),
    path('password_reset/', auth_views.PasswordResetView.as_view(template_name='users/password_reset.html'), name='password_reset'), 
    path('password_reset/done', auth_views.PasswordResetDoneView.as_view(template_name='users/password_reset_done.html'), name='password_reset_done'),
    path('password_reset_confirm/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(template_name='users/password_reset_confirm.html'), name='password_reset_confirm'),
    path('password_reset_complete', auth_views.PasswordResetCompleteView.as_view(template_name='users/password_reset_complete.html'), name='password_reset_complete'),

    path('verification/', include('verify_email.urls')),
    path('resend_email_verification/', resend_email_verification, name='resend_email_verification'), #deneme
]

if settings.DEBUG: 
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

在您的 AccountUpdate 表单中您写了:

elif self.instance.pk:
    self.fields['city'].queryset = self.instance.country.city_set.order_by('name')

但用户可能没有国家,所以将其更改为:

elif self.instance.pk and self.instance.country:
    self.fields['city'].queryset = self.instance.country.city_set.order_by('name')