2009-09-27 63 views
18

Tengo un modelo, llamado Estudiante, que tiene algunos campos, y una relación de OneToOne con un usuario (django.contrib.auth.User).Formulario modelo django. Incluir campos de los modelos relacionados

class Student(models.Model): 

    phone = models.CharField(max_length = 25) 
    birthdate = models.DateField(null=True) 
    gender = models.CharField(max_length=1,choices = GENDER_CHOICES) 
    city = models.CharField(max_length = 50) 
    personalInfo = models.TextField() 
    user = models.OneToOneField(User,unique=True) 

Entonces, tengo una ModelForm para ese modelo

class StudentForm (forms.ModelForm): 
    class Meta: 
     model = Student 

El uso de los campos de atributo en la clase Meta, he logrado mostrar sólo algunos campos en una plantilla. Sin embargo, ¿puedo indicar qué campos de usuario mostrar?

Algo como:

fields =('personalInfo','user.username') 

actualmente no se muestra nada. Funciona solo con StudentFields/

Gracias de antemano.

+0

si el modelo del estudiante heredó el modelo de usuario al que sólo necesita una ModelForm. –

+0

@KevinL., Sería genial si elaboras esto en una respuesta :-) – cel

+0

@cel AFAIK, no ha habido desarrollos como este para hablar en el núcleo de Django. Una solución 'automática' aquí probablemente no sea trivial e involucre escribir sus propias clases de modelos a medida o mixins para hacerlo. Esto sería significativamente más complejo (y podría decirse que más frágil) que utilizar los métodos sugeridos. Una posible solución podría estar contenida en [esta respuesta] (https://stackoverflow.com/a/41559015/5747944) que describe una mezcla ModelForm que permite definir un segundo modelo 'hijo' y se dice que es compatible con vistas genéricas. – sytech

Respuesta

7

Ambas respuestas son correctas: Inline Formsets hacen hacer esto fácil.

Tenga en cuenta, sin embargo, que el en línea solo puede ir en una dirección: desde el modelo que tiene la clave foránea en él. Sin tener claves primarias en ambos (mal, ya que podría tener A -> B y luego B -> A2), no puede tener el formset en línea en el modelo related_to.

Por ejemplo, si tiene una clase UserProfile, y desea poder tenerlas, cuando se muestre, tenga el objeto User relacionado que se muestra como en línea, no tendrá suerte.

Puede tener campos personalizados en un ModelForm, y utilizar esto como una forma más flexible, pero tenga en cuenta que ya no es 'automático' como un formset estándar ModelForm/en línea.

+0

Estoy golpeando exactamente el problema descrito en esta pregunta y su respuesta. Ahora han pasado muchos años. ¿Sabes si hay una solución automática ahora? – cel

4

Una práctica común es usar 2 formularios para lograr su objetivo.

  • Una forma para la User Modelo:

    class UserForm(forms.ModelForm): 
        ... Do stuff if necessary ... 
        class Meta: 
         model = User 
         fields = ('the_fields', 'you_want') 
    
  • Una forma para la Student Modelo:

    class StudentForm (forms.ModelForm): 
        ... Do other stuff if necessary ... 
        class Meta: 
         model = Student 
         fields = ('the_fields', 'you_want') 
    
  • Uso, ambas formas en su vista (ejemplo de uso):

    def register(request): 
        if request.method == 'POST': 
         user_form = UserForm(request.POST) 
         student_form = StudentForm(request.POST) 
         if user_form.is_valid() and student_form.is_valid(): 
          user_form.save() 
          student_form.save() 
    
  • Render las formas juntos en su plantilla:

    <form action="." method="post"> 
        {% csrf_token %} 
        {{ user_form.as_p }} 
        {{ student_form.as_p }} 
        <input type="submit" value="Submit"> 
    </form> 
    

Otra opción sería para usted para cambiar la relación OneToOne-ForeignKey (esto depende totalmente de usted y yo sólo mencionas, no lo recomiendo) y use el inline_formsets para lograr el resultado deseado.

+1

@cel No sé de una manera "automática" (como usted dice) pero tengo una solución manual en esta respuesta. Echar un vistazo :) –

2

Un método alternativo que podría considerar es crear un modelo de usuario personalizado extendiendo los modelos AbstractUser o AbstractBaseUser en lugar de utilizar un modelo one-to-one link con un modelo de perfil (en este caso, el modelo de estudiante). Esto crearía un único modelo de Usuario extendido que puede usar para crear un único Modelo.

Por ejemplo, una manera de hacer esto sería extender el AbstractUser model:

from django.contrib.auth.models import AbstractUser 

class Student(AbstractUser): 

    phone = models.CharField(max_length = 25) 
    birthdate = models.DateField(null=True) 
    gender = models.CharField(max_length=1,choices = GENDER_CHOICES) 
    city = models.CharField(max_length = 50) 
    personalInfo = models.TextField() 
    # user = models.OneToOneField(User,unique=True) <= no longer required 

En archivo settings.py, actualizar el AUTH_USER_MODEL

AUTH_USER_MODEL = 'appname.models.Student' 

actualización del modelo en su administración:

from django.contrib import admin 
from django.contrib.auth.admin import UserAdmin 
from .models import Student 

admin.site.register(Student, UserAdmin) 

continuación, puede utilizar un solo ModelForm que tiene tanto los campos adicionales yo Necesitas tan bien como los campos en el modelo de Usuario original. En forms.py

from .models import Student  

class StudentForm (forms.ModelForm): 
    class Meta: 
     model = Student 
     fields = ['personalInfo', 'username'] 

Una forma más complicada sería extender la AbstractBaseUser, esto se describe en detalle in the docs.

Sin embargo, no estoy seguro de si la creación de un modelo de usuario personalizada de esta manera con el fin de tener una sola ModelForm conveniente tiene sentido para su caso de uso. Esta es una decisión de diseño que debe tomar ya que la creación de modelos de usuario personalizados puede ser un ejercicio complicado.

1

Si no desea cambiar el AUTH_USER_MODEL que tiene muchos efectos secundarios, puede utilizar Multi-table inheritance y subclase el modelo usuario en lugar de AbstractUser. Esto creará una mesa deEstudiante con una user_ptrOneToOneField llamado que apunta a la mesa deusuario.

He aquí un ejemplo

from django.contrib.auth.models import User 
from django.db import models 
from django.utils.translation import gettext_lazy as _ 


class Student(User): 
    phone = models.CharField(max_length=25) 
    birthdate = models.DateField(null=True) 
    city = models.CharField(max_length=50) 
    personalInfo = models.TextField() 

    class Meta: 
     verbose_name = _('student') 
     verbose_name_plural = _('students') 

Ahora puede definir su ModelForm como esto

class StudentForm(forms.ModelForm): 
    class Meta: 
     model = Student 
     fields = ('first_name', 'last_name', 'username', 
        'personalInfo', 'phone', 'birthdate', 'city') 

También puede extender el construido en formularios de usuario de Django como este

from django.contrib.auth.forms import UserChangeForm 

class StudentForm(UserChangeForm): 
    class Meta: 
     model = Student 
     fields = ('first_name', 'last_name', 'username', 
       'personalInfo', 'phone', 'birthdate', 'city') 

Para usar su formulario en django admin, agregue t lo siguiente para admin.py:

from django.contrib import admin 
from .views import StudentForm 
from .models import Student 


class StudentAdmin(admin.ModelAdmin): 
    form = StudentForm 

admin.site.register(Student, StudentAdmin) 

por Sub clasificando el modelo usuario, creando un estudiante ejemplo creará automáticamente una nueva instancia de usuario pero no al revés. Por lo que un ejemplo El usuario puede existir sin estar asociado a un estudiante ejemplo.Si desea asegurarse de que se crea una instancia de Student para cada usuario en el sistema, puede utilizar la siguiente señal:

from django.contrib.auth.models import User 
from django.db.models.signals import post_save 
from django.dispatch import receiver 

from .models import Student 


@receiver(post_save, sender=User) 
def create_student(sender, instance, created, **kwargs): 
    if created: 
     student = Student(user_ptr_id=instance.pk) 
     student.__dict__.update(instance.__dict__) 
     student.save() 
1

Desde mi entender desea actualizar el campo de nombre de usuario de auth.user que es OneToOne relación con Student, esto es lo que yo haría ...

class StudentForm (forms.ModelForm): 

username = forms.Charfield(label=_('Username')) 
class Meta: 
    model = Student 
    fields = ('personalInfo',) 

def clean_username(self): 
    # using clean method change the value 
    # you can put your logic here to update auth.User 
    username = self.cleaned_data('username') 
    # get AUTH USER MODEL 
    in_db = get_user_model()._default_manager.update_or_create(username=username) 

esperanza de que esto ayude :)

Cuestiones relacionadas