我有一个自定义用户模型,我在其中处理三个用户配置文件StudentProfile
:ProfessorProfile
和ExecutiveProfile
. 这些模型中的每一个都与我的User
自定义模型相关
对于这三个配置文件,我有三个继承自 的表单form.ModelForms
,其中生成每个用户配置文件的字段:
class StudentProfileForm(forms.ModelForm):
class Meta:
model = StudentProfile
fields = ('origin_education_school', 'current_education_school',
'extra_occupation')
class ProfessorProfileForm(forms.ModelForm):
class Meta:
model = ProfessorProfile
fields = ('occupation',)
class ExecutiveProfileForm(forms.ModelForm):
class Meta:
model = ExecutiveProfile
fields = ('occupation', 'enterprise_name', 'culturals_arthistic','ecological')
要访问此用户配置文件视图,我创建了此 URL
url(r"^profile/(?P<slug>[\w\-]+)/$",
views.AccountProfilesView.as_view(),
name='profile'
),
此外,我的观点AccountProfilesView
有以下行为:
从基于类的视图来看AccountProfilesView
,这些表单中的每一个都被实例化,因为用户的配置文件是对应的,因此如果用户拥有配置文件is_student
,则会为配置文件is_professor
e生成其对应的表单,依此类推is_executive
。
如果用户拥有三个配置文件 ( is_student
, is_professor
e is_executive
),则将为与这些配置文件关联的相应数据创建三个表单的字段。
class AccountProfilesView(LoginRequiredMixin, UpdateView):
# All users can access this view
model = get_user_model()
success_url = reverse_lazy('dashboard')
template_name = 'accounts/profile_form.html'
fields = '__all__'
def get_context_data(self, **kwargs):
context = super(AccountProfilesView, self).get_context_data(**kwargs)
user = self.request.user
if not self.request.POST:
if user.is_student:
profile = user.get_student_profile()
context['userprofile'] = profile
context['form_student'] = forms.StudentProfileForm()
if user.is_professor:
profile = user.get_professor_profile()
context['userprofile'] = profile
context['form_professor'] = forms.ProfessorProfileForm()
# print ("profesor form is", context['form_professor'])
if user.is_executive:
profile = user.get_executive_profile()
context['userprofile'] = profile
context['form_executive'] = forms.ExecutiveProfileForm()
else:
if user.is_student:
context['form_student'] = forms.StudentProfileForm(
self.request.POST)
if user.is_professor:
context['form_professor'] = forms.ProfessorProfileForm(
self.request.POST)
if user.is_executive:
context['form_executive'] = forms.ExecutiveProfileForm(
self.request.POST)
return context
def form_valid(self, form):
context = self.get_context_data(form=form)
user = self.request.user
user = form.save()
if user.is_student:
student = context['form_student'].save(commit=False)
student.user = user
student.save()
if user.is_professor:
professor = context['form_professor'].save(commit=False)
professor.user = user
professor.save()
if user.is_executive:
executive = context['form_executive'].save(commit=False)
executive.user = user
executive.save()
return super(AccountProfilesView, self).form_valid(form)
在我的模板中,我有以下小逻辑:
<form method="POST">
{% csrf_token %}
{% if userprofile.user.is_student %}
<div align="center"><i>My Student Profile data</i></div>
{% bootstrap_form form_student %}
{% endif %}
{% if userprofile.user.is_professor %}
<div align="center"><i>My Professor Profile data</i></div>
{% bootstrap_form form_professor %}
{% endif %}
{% if userprofile.user.is_executive %}
<div align="center"><i>My Executive Profile data</i></div>
{% bootstrap_form form_executive %}
{% endif %}
<input type="submit" value="Save Changes" class="btn btn-default">
</form>
表单是根据用户的个人资料显示的,但是发生在我身上的是,当我提交时,表单没有将任何内容保存到数据库中……而且,success_url
它的行为很疯狂,因为它没有将我重定向到 URL我想要的是dashboard/
,但它保留在同一个模板中,但我失去了使用我的用户登录的操作,也就是说,它让我对 LoginRequiredMixin() 没有任何操作。
在其中,我对方法执行的覆盖form_valid()
是我有记录相应表单数据的指令的地方。
方法覆盖有问题form_valid()
吗?
更新
根据@German-Alzate给出的答案以及他使用基于函数的视图的建议,我想补充我的场景的一些细节,并提出一些从给定实现中产生的问题:
在我的自定义用户模型中,我有方法get_student_profile()
,get_professor_profile()
并get_executive_profile()
在我的视图中获取每个用户的数据。它们如下:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
username = models.CharField(max_length=40, unique=True)
slug = models.SlugField(max_length=100, blank=True)
is_student = models.BooleanField(default=False)
is_professor = models.BooleanField(default=False)
is_executive = models.BooleanField(default=False)
def get_student_profile(self):
student_profile = None
if hasattr(self, 'studentprofile'):
student_profile = self.studentprofile
return student_profile
def get_professor_profile(self):
professor_profile = None
if hasattr(self, 'professorprofile'):
professor_profile = self.professorprofile
return professor_profile
def get_executive_profile(self):
executive_profile = None
if hasattr(self, 'executiveprofile'):
executive_profile = self.executiveprofile
return executive_profile
补充更新
当我使用任何配置文件创建用户时is_student
,is_professor
我is_executive
有一个信号会自动获取字段的值username
并将其保存在slug
模型字段中User
@receiver(post_save, sender=User)
def post_save_user(sender, instance, **kwargs):
slug = slugify(instance.username)
User.objects.filter(pk=instance.pk).update(slug=slug)
通过这种方式,我使用该字段slug
在每个用户的首选项和个人资料 url 中显示它。
并且每个用户配置文件都有自己的模型相关模型User
,加上一个 slug 字段,该字段username
取其自己的模型相关用户的值User
。
正是通过这种方式,在模型User
中,我重写了该方法save()
以指示username
正在创建的用户的字段的值与slug
该用户将采用的配置文件的字段的值相等(StudentProfile
, ProfessorProfile
, ExecutiveProfile
):
def save(self, *args, **kwargs):
user = super(User,self).save(*args,**kwargs)
# Creating an user with student, professor and executive profiles
if self.is_student and not StudentProfile.objects.filter(user=self).exists() \
and self.is_professor and not ProfessorProfile.objects.filter(user=self).exists() \
and self.is_executive and not ExecutiveProfile.objects.filter(user=self).exists():
student_profile = StudentProfile(user=self)
student_slug = self.username
student_profile.slug = student_slug
professor_profile = ProfessorProfile(user=self)
professor_slug = self.username
professor_profile.slug = professor_slug
executive_profile = ExecutiveProfile(user=self)
executive_slug = self.username
executive_profile.slug = executive_slug
student_profile.save()
professor_profile.save()
executive_profile.save()
并且每个配置文件的模型(StudentProfile
, ProfessorProfile
, ExecutiveProfile
):是:
class StudentProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
slug = models.SlugField(max_length=100,blank=True)
origin_education_school = models.CharField(max_length=128)
current_education_school = models.CharField(max_length=128)
extra_occupation = models.CharField(max_length=128)
class ProfessorProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
slug = models.SlugField(max_length=100,blank=True)
class ExecutiveProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
slug = models.SlugField(max_length=100,blank=True)
我这样说是为了在我的代码中说明我在模型User
中有一个 slug 字段,它采用用户名字段的值,并且我在每个用户配置文件中都有一个字段,用于显示我的 n 个表单slug
的配置文件 urlprofile/<slug>
需要从配置文件
但是在我们基于函数的视图account_profiles_view()
中,所做的是重新记录该配置文件数据,或创建这些配置文件所属的新用户实例,因为当我去编辑它们时,我并没有更新它们,而是系统尝试用相同的id
或创建一个额外的记录,pk
它会产生一个IntegrityError
,这很明显,因为我们从来没有说我们正在更新,对吧?
django.db.utils.IntegrityError: duplicate key value violates unique constraint "accounts_studentprofile_user_id_key"
DETAIL: Key (user_id)=(14) already exists.
事实证明,这(user_id)=(14)
是我的模型中该用户的 id 或主键User
,但在我的模型中StudentProfile
(因为它具有该配置文件)它id=7
在 ProfessorProfile 中有一个id=8
,在 ExecutiveProfile 中有一个id=7
因为我想要的是让我的功能account_profiles_view()
允许我更新用户的个人资料数据,无论他们的个人资料是什么,那么我认为(如果我当然没记错的话)我应该以某种方式更新该用户的个人资料记录.通过解释请求中出现的用户ID的方式,像这样?:
User = get_object_or_404(User, pk=pk)
也就是说,我的函数除了请求之外account_profiles_view()
还有一个属性作为参数pk
,并且这个更新应该在验证操作将是一个方法之后进行。POST
问题是我不知道该怎么做,我认为account_profiles_view()
根据您构建它的方式,我的功能最终会是这样的,增加了更新其个人资料的用户注册的可能性:
@login_required
# No tengo claro si con estos tres parámetros, se supone que si
# porque el slug lo necesito si o si para visualizar el perfil
def account_profiles_view(request, slug, pk):
user = request.user
_forms = []
if user.is_student:
profile = user.get_student_profile()
_forms.append(forms.StudentProfileForm)
if user.is_professor:
profile = user.get_professor_profile()
_forms.append(forms.ProfessorProfileForm)
if user.is_executive:
profile = user.get_executive_profile()
_forms.append(forms.ExecutiveProfileForm)
if request.method == 'POST':
formularios =[Form(data = request.POST) for Form in _forms]
if all([form.is_valid() for form in formularios]):
for form in formularios:
# No tengo claro como incluir esta parte del update
# y como indicarla en las operaciones posteriores que
# me explicabas
user = get_object_or_404(User, pk=pk)
profile = form.save(commit=False)
profile.user = user
profile.save()
return redirect('dashboard')
else:
formularios = [Form() for Form in _forms]
data = {form.__class__.__name__.__str__().lower(): form for form in formularios}
data['userprofile'] = profile
return render(request, 'accounts/profile_form.html', data,)
目前,我尝试“更新”用户个人资料时的结果是:
很明显,为什么我不进行更新,但是如果问题出在 del 行,profile.save()
但是如果我使变量profile
取user
更新 del的值pk = pk
,那么事情会起作用吗?
这是我不确定的事情,我做到了,但是如果可能的话,我想与您分享以供您指导,因为它不起作用
添加更新结束
在我评估每个请求时,我在我的视图中使用这些方法,它是什么类型的用户,也调用他们的个人资料,以这种方式并寻求以某种方式将这些数据发送到模板,或者通过一些上下文/字典或者使用函数render()
,就像我们在基于函数的视图中做所有事情一样。
这将是我将继续做的事情
if user.is_student:
profile = user.get_student_profile()
_forms.append(forms.StudentProfileForm)
if user.is_professor:
profile = user.get_professor_profile()
_forms.append(forms.ProfessorProfileForm)
if user.is_executive:
profile = user.get_executive_profile()
_forms.append(forms.ExecutiveProfileForm)
return render({'userprofile':profile})
# Aquí ¿enviaría los datos de perfil de un usuario
# en caso de tener los tres perfiles?
在这种情况下,基于功能的观点以及德国人在他的回答中提出的观点是这样的:
@login_required
def account_profiles_view(request, slug):
user = request.user
_forms = []
if user.is_student:
profile = user.get_student_profile()
_forms.append(forms.StudentProfileForm)
if user.is_professor:
profile = user.get_professor_profile()
_forms.append(forms.ProfessorProfileForm)
if user.is_executive:
profile = user.get_executive_profile()
_forms.append(forms.ExecutiveProfileForm)
if request.method == 'POST':
formularios =[Form(data = request.POST) for Form in _forms]
if all([form.is_valid() for form in formularios]):
for form in formularios:
profile = form.save(commit=False)
profile.user = user
profile.save()
return redirect('dashboard')
else:
formularios = [Form() for Form in _forms]
data = {form.__name__.__str__.lower(): form for form in formularios}
return render(request, 'accounts/profile_form.html', data, {'userprofile':profile})
在我的模板中,我不知道我是否正确调用了从视图提交的每个表单实例:
<form method="POST">
{% csrf_token %}
{% if userprofile.user.is_student %}
<div align="center"><i>My Student Profile data</i></div>
{% bootstrap_form studentprofileform %}
{% endif %}
{% if userprofile.user.is_professor %}
<div align="center"><i>My Professor Profile data</i></div>
{% bootstrap_form professorprofileform %}
{% endif %}
{% if userprofile.user.is_executive %}
<div align="center"><i>My Executive Profile data</i></div>
{% bootstrap_form executiveprofileform %}
{% endif %}
<input type="submit" value="Save Changes" class="btn btn-default">
</form>
当我测试我的个人资料/用户名网址时
url(r"^profile/(?P<slug>[\w\-]+)/$",
views.account_profiles_view,
name='profile'
),
我看到这个错误:
File "/home/bgarcial/workspace/ihost_project/accounts/views.py", line 233, in account_profiles_view
data = {form.__name__.__str__.lower(): form for form in formularios}
File "/home/bgarcial/workspace/ihost_project/accounts/views.py", line 233, in <dictcomp>
data = {form.__name__.__str__.lower(): form for form in formularios}
AttributeError: 'StudentProfileForm' object has no attribute '__name__'
[10/Apr/2017 18:43:40] "GET /accounts/profile/zidane/ HTTP/1.1" 500 86615
Viendo el traceback veo que el inconveniente se referencia a la forma en como se están enviando al template en las instancias de formularios creadas en el diccionario data
No estoy muy familiarizado con la forma en como llevar a cabo el concepto de compresión de listas y diccionarios (que creo que es la forma en como lo estas haciendo ¿cierto?, me corriges si me equivoco por favor) pero entiendo que lo que se hace aquí es:
si la operación de request
de un usuario es POST
, se genera una lista
con todos los forms en donde se haya hecho dicha operación (es decir si esta en uno o dos o los tres perfiles de campos de formularios ).
Acto seguido, si todos los formularios contenidos en esa lista son validos
if all([form.is_valid() for form in formularios]): ...
Itere sobre cada uno y guardelos
Y por ultimo se forma el diccionario data
en donde se envia cada instancia del formulario tomando su nombre respectivo, convirtiendolo a minúsculas y como son tres formularios se itera sobre la lista con todos los formularios generados.
Entiendo, que eso es lo que se hace aquí ¿verdad?
data = {form.__name__.__str__.lower(): form for form in formularios}
Y es bastante coherente y con sentido el planteamiento e implementación. La cosa es que al obtener el mensaje de:
AttributeError: 'StudentProfileForm' object has no attribute '__name__'
No acabo de entender porque para tomar el nombre sería '__name__'
pues creo que fue una convención que utilizaste para ilustrarme acerca de como sería la solución, y que en lugar de __name__
para tomar un nombre como el que se intenta, ¿el sistema esta buscando o debe buscar un atributo que este en los formularios cuando fueron definidos?
Es decir ¿cualquiera de los campos de cada formulario que venga?
Pero no le hayaría sentido poner un nombre de campo, dado que en cad request de url profile
pueden venir o StudentProfileForm
o ProfessorProfileForm
o ExecutiveProfileForm
, pero cada uno tiene nombres propios de campos.
Por esta razón es que veo que en el planteamiento e implementación German intenta darle que el nombre de clave para el formulario que se envie sea el nombre del mismo y en minusculas para llamarlo en el template, lo que tiene sentido, pero entonces el problema de AttributeError: 'StudentProfileForm' object has no attribute '__name__'
que no acabo de comprender.
Pero que veo que con su planteamiento voy un paso mas cerca de obtener la solución, dado que en el request en el traceback estan todos los campos de los formularios que se envian.
La mejor opción como te comentaba en los comentarios, es hacer de tu CBV un FBV donde tendrías mas control, por algunos temas que te comentare mas adelante, de modo que quedaría algo como así:
views.py
Como ves, no me metí con el método
get_user_profile
porque no se lo que hace cada método, pero esto es una forma de verificar todos los formularios, y los recogerias desde el template como{{ studentprofileform }}
tendrías todo más controlado. Esta forma es un poco automatizada, podrías hacerla mas manual y personalizada, que igual trabajarás mejor. Asegurate de mostrar los errores de los formularios para los usuarios desde el template, para que siempre lleguen los formularios adecuadamente llenos.Ahora, en una CBV, el proceso es un tanto diferente, ya que las CBV estan diseñadas para usar un solo formulario, en tu caso usarás tantos formularios como permisos tenga el usuario, por lo que no es muy cómodo. Solo para darte una idea de lo que seria trabajar esto en CBV sería algo así:
views.py
Porque al momento de hacer un POST, las CBV solo verificaran el formulario que obtengan por medio de
get_form_class
y ese formulario será el único que van a proceder a validar. Se puede llegar a validar los 3 formularios a la vez, pero tendrías que sobreescribir muchas partes de código y lo mas sencillo y rápido, será usar un FBV.Cualquier duda, comenta.
ACTUALIZACIÓN
Actualicé la parte donde te arrojaba error:
Lo cambié por:
Que básicamente para responder las otras preguntas, todo en python es un objeto, y la forma de crear objetos es mediante una clase, a la cual desde una instancia, se puede acceder mediante la propiedad 'magica' o 'privada'
__class__
, recuerda que este retorna la clase sin mas, y esta clase, por defecto tiene otra propiedad privada llamada__name__
el cual te retorna el nombre en string de una clase, que será el mismo nombre con el que la llamaste, es decir con el que creas la clase, el que va luego de la palabra reservadaclass
, luego llamo a su método en string, para asegurarme que siempre retorne un string y llamo a lower, la idea de hacer esto es poder ubicarlo en un diccionario de python y que puedas acceder a ello luego de una forma mas facil y legible para el humano. Esto solucionaria elAttributeError
Lo de data, es un simple diccionario de python, el cual será las variables que el django internamente enviará al procesador de contextos y su motor de plantillas para hacer el render de los templates. Consta de llave: valor, en este caso en la compresión de listas, lo que estamos haciendo es algo parecido a esto:
La explicación de esta linea:
Es que usted solo debería guardar datos en la base de datos, si TODOS los formularios que le envió al usuario están correctos, de no ser así, esta linea no tendría sentido y habría que replantearla. Por eso verifico primero que todos los formularios hayan sido llenados.
最后,解决了几个疑问,我想帮助您澄清配置文件的部分,我前一段时间没有对配置文件做任何事情的原因是您正在覆盖它们,每次输入if时,它都会覆盖它对应一个值,所以如果你这样离开它,你的代码会有问题,让我们分析一下,我建议我是一个用户,一个学生,一个行政人员,也是一个老师:
如果您查看评论,您会注意到该变量
profile
取决于您提出 if 问题的顺序,并且如果评估为 True,则始终会获得最后一个的值。但是撇开这一点不谈,我不知道您对配置文件的重视程度,但是,将所述变量发送到模板的正确方法是: