Форма модели django. Включить поля из связанных моделей

У меня есть модель, называемая Student, у которой есть некоторые поля, и отношения OneToOne с пользователем (django.contrib.auth.User).

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)

Затем у меня есть ModelForm для этой модели

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

Используя атрибут полей в классе Meta, мне удалось отобразить только некоторые поля в шаблоне. Однако могу ли я указать, какие поля пользователя показывать?

Что-то вроде:

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

в настоящее время ничего не показывает. Работает только с StudentFields, хотя /

Спасибо заранее.

Ответ 1

Оба ответа верны: встроенные Formsets делают это легко.

Помните, однако, что встроенная строка может идти только в одну сторону: от модели, в которой есть внешний ключ. Без наличия первичных ключей в обоих (плохо, поскольку вы могли бы тогда иметь A → B, а затем B → A2), вы не можете иметь встроенный набор форм в связанной модели.

Например, если у вас есть класс UserProfile и вы хотите иметь их, когда показано, есть объект User, который связан, как показано на рисунке, вам не повезло.

У вас могут быть настраиваемые поля в ModelForm и использовать это как более гибкий способ, но имейте в виду, что он больше не является "автоматическим", как стандартный набор форм ModelForm/inline.

Ответ 2

Общей практикой является использование двух форм для достижения вашей цели.

  • Форма для User Модель:

    class UserForm(forms.ModelForm):
        ... Do stuff if necessary ...
        class Meta:
            model = User
            fields = ('the_fields', 'you_want')
    
  • Форма для Student Модель:

    class StudentForm (forms.ModelForm):
        ... Do other stuff if necessary ...
        class Meta:
            model = Student
            fields = ('the_fields', 'you_want')
    
  • Используйте обе формы в своем представлении (пример использования):

    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()
    
  • Вставьте формы вместе в свой шаблон:

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

Другим вариантом было бы изменить отношение от OneToOne до ForeignKey (это полностью зависит от вас, и я просто упомянул об этом, не рекомендую его) и используйте inline_formsets для достижения желаемого результата.

Ответ 3

Альтернативным методом, который можно было бы рассмотреть, является создание пользовательской модели путем расширения моделей AbstractUser или AbstractBaseUser вместо использования ссылки "один-к-одному" с помощью Модель профиля (в данном случае модель Студента). Это создало бы одну расширенную модель пользователя, которую можно использовать для создания единственного ModelForm.

Например, одним из способов сделать это будет расширение 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

В файле settings.py обновите AUTH_USER_MODEL

AUTH_USER_MODEL = 'appname.models.Student'

обновите модель в своем Admin:

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

admin.site.register(Student, UserAdmin)

Затем вы можете использовать один ModelForm, который имеет как дополнительные поля, которые вам нужны, так и поля в исходной модели пользователя. В forms.py

from .models import Student    

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

Более сложным способом было бы расширить AbstractBaseUser, это подробно описано в документах.

Тем не менее, я не уверен, создает ли пользовательскую пользовательскую модель таким образом, чтобы иметь удобную единую модель ModelForm для вашего случая использования. Это дизайнерское решение, которое вы должны сделать, поскольку создание пользовательских моделей пользователей может быть сложным упражнением.

Ответ 4

Если вы не хотите менять AUTH_USER_MODEL, у которого много побочных эффектов, вы можете использовать Наследование на несколько таблиц и подкласс модели Пользователь вместо AbstractUser. Это создаст таблицу Студент с OneToOneField с именем user_ptr, которая указывает на таблицу Пользователь.

Здесь пример

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

Теперь вы можете определить ModelForm как

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

Вы также можете расширить встроенные пользовательские формы django, такие как

from django.contrib.auth.forms import UserChangeForm

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

Чтобы использовать вашу форму в администраторе django, добавьте следующее в 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)

Посредством класса Пользователь создание экземпляра Студент автоматически создаст новый экземпляр пользователя, но не наоборот. Таким образом, экземпляр Пользователь может существовать без связи с экземпляром Студент. Если вы хотите убедиться, что экземпляр Student создан для каждого Пользователь в системе, вы можете использовать следующий сигнал:

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()

Ответ 5

Из моего понимания вы хотите обновить поле имени пользователя auth.User, которое имеет отношение OneToOne с Student, это то, что я бы сделал...

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)

надеюсь, что это поможет:)