社交 django 'NoneType' 对象没有属性 '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,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,password):
        user = self.create_user( #if email and username condition passes in the top,# 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,null=True)
    first_name          = models.CharField(max_length=100,null=True)
    last_name           = models.CharField(max_length=100,null=True)
    GENDER_LIST=(
        ('Male','Male'),('Female','Female')
        )
    gender              = models.CharField(max_length=100,choices=GENDER_LIST,null=True) 
    birthday            = models.DateField(help_text='Kindly provide a global date formatting as follows: (YYYY-MM-DD)',auto_Now=False,null=True)
    country             = models.ForeignKey(Country,on_delete=models.SET_NULL,null=True)
    city                = models.ForeignKey(City,null=True)
    district            = models.CharField(max_length=100,null=True)
    image               = models.ImageField(default='default.jpg',upload_to='profile_pics')
    slug                = models.SlugField(max_length=200,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,**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',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,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,UpdateView):
    model = Account
    form_class = AccountUpdateForm
    # fields = ('email','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,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/',name='resend_email_verification'),#deneme
]

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

解决方法

在您的 AccountUpdate 表单中,您写了:

// Set graduationDate to enrollmentDate,then add 4 years
graduationDate = new GregorianCalendar(); // New object
graduationDate.setTime(enrollmentDate.getTime()); // Update time
graduationDate.add(GregorianCalendar.YEAR,4); // add years

但用户可能没有国家/地区,因此将其更改为:

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