I have a model, called Student, which has some fields, and a OneToOne relationship with a user (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)
Then, I have a ModelForm for that model
class StudentForm (forms.ModelForm):
class Meta:
model = Student
Using the fields attribute in class Meta, I've managed to show only some fields in a template. However, can I indicate which user fields to show?
Something as:
fields =('personalInfo','user.username')
is currently not showing anything. Works with only StudentFields though/
Thanks in advance.
A common practice is to use 2 forms to achieve your goal.
A form for the User Model:
class UserForm(forms.ModelForm):
... Do stuff if necessary ...
class Meta:
model = User
fields = ('the_fields', 'you_want')
A form for the Student Model:
class StudentForm (forms.ModelForm):
... Do other stuff if necessary ...
class Meta:
model = Student
fields = ('the_fields', 'you_want')
Use both those forms in your view (example of usage):
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 the forms together in your template:
<form action="." method="post">
{% csrf_token %}
{{ user_form.as_p }}
{{ student_form.as_p }}
<input type="submit" value="Submit">
</form>
Another option would be for you to change the relationship from OneToOne to ForeignKey (this completely depends on you and I just mention it, not recommend it) and use the inline_formsets to achieve the desired outcome.
Both answers are correct: Inline Formsets make doing this easy.
Be aware, however, that the inline can only go one way: from the model that has the foreign key in it. Without having primary keys in both (bad, since you could then have A -> B and then B -> A2), you cannot have the inline formset in the related_to model.
For instance, if you have a UserProfile class, and want to be able to have these, when shown, have the User object that is related shown as in inline, you will be out of luck.
You can have custom fields on a ModelForm, and use this as a more flexible way, but be aware that it is no longer 'automatic' like a standard ModelForm/inline formset.
An alternative method that you could consider is to create a custom user model by extending the AbstractUser or AbstractBaseUser models rather than using a one-to-one link with a Profile model (in this case the Student model). This would create a single extended User model that you can use to create a single ModelForm.
For instance one way to do this would be to extend the 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
In settings.py file, update the AUTH_USER_MODEL
AUTH_USER_MODEL = 'appname.models.Student'
update the model in your Admin:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import Student
admin.site.register(Student, UserAdmin)
Then you can use a single ModelForm that has both the additional fields you require as well as the fields in the original User model. In forms.py
from .models import Student
class StudentForm (forms.ModelForm):
class Meta:
model = Student
fields = ['personalInfo', 'username']
A more complicated way would be to extend the AbstractBaseUser, this is described in detail in the docs.
However, I'm not sure whether creating a custom user model this way in order to have a convenient single ModelForm makes sense for your use case. This is a design decision you have to make since creating custom User models can be a tricky exercise.
From my understanding you want to update the username field of auth.User which is OneToOne relation with Student, this is what I would do...
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)
hope this helps :)
Related
I am using the default User model in Django and a OneToOneField in a Profile model where extra info such as user bio is stored.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
I am able to create basic forms for the two models independently
from django.contrib.auth.models import User
class UserForm(ModelForm):
class Meta:
model = User
fields = ['username', 'email']
class ProfileForm(ModelForm):
class Meta:
model = Profile
fields = ['bio']
What is the best method to create a page where a user can edit fields of either model?
So a User can edit Username,Email or Bio on the same page?
You can put the 2 forms in one template and Django will manage filling forms with the right fields (only exception is the same field name in 2 forms)
def view(request):
if request.method == "GET":
context["userform"]=UserForm()
context["profileform"] =ProfileForm()
else:
userform = UserForm(request.POST)
profileform=ProfileForm(request.POST)
Trying to create a choice field based on another model
I want my choices to be mapped like username: first_name + last_name
When I try username: last_name it does work
I tried doing something like this(Note, I am adding on user_choices and choices=user_choices. The model already existed before me making these changes.)
This works:
he_user_choices = tuple(User.objects.values_list('username', 'last_name'))
Here's what my models.py looks like:
from django.contrib.auth.models import User
owner_choices = tuple(User.objects.values_list('username', 'first_name' + 'last_name'))
class ErrorEvent(models.Model):
"""Error Event Submissions"""
event_id = models.BigAutoField(primary_key=True)
owner = models.IntegerField(verbose_name="Owner", blank=True, choices=owner_choices)
and here's my forms.py
from django import forms
from .models import ErrorEvent
class ErrorEventForm(forms.ModelForm):
class Meta:
model = ErrorEvent
# fields =
exclude = ['event_id']
widgets = {
'owner': forms.Select(),
}
Currently the owner_choices doesn't work, I get an error that says:
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
Any recommendations on what else I can try, or how would I go about fixing my problem?
Thank you in advance!
Please do not work with an IntegerField to refer to an object. A ForeignKey will check referential integrity, and furthermore it makes the Django ORM more expressive.
You thus can implement this with:
from django.conf import settings
class ErrorEvent(models.Model):
event_id = models.BigAutoField(primary_key=True)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name='Owner',
blank=True,
null=True
)
This will work with a ModelChoiceField [Django-doc] that automatically will query the database to render options. This also means that if you add a new User, creating a new ErrorEvent can be linked to that user, since it each time requests the Users from the database.
You can subclass the ModelChoiceField to specify how to display the options, for example:
from django.forms import ModelChoiceField
class MyUserModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return f'{obj.username} ({obj.firstname} {obj.lastname})'
Then we can use this in the form:
class ErrorEventForm(forms.ModelForm):
owner = MyUserModelChoiceField(queryset=User.objects.all())
class Meta:
model = ErrorEvent
# fields =
exclude = ['event_id']
widgets = {
'owner': forms.Select(),
}
I have the following models:
class UserProfile(models.Model):
user = models.OneToOneField(User)
class Property(models.Model):
user = models.ForeignKey(User)
I would like to create a TabularInline displaying every Property connected to a particular UserProfile on its Django admin page. The problem here is, of course, that Property does not have a ForeignKey directly to UserProfile, so I cannot simply write
class PropertyTabularInline(admin.TabularInline):
model = Property
class UserProfileAdmin(admin.ModelAdmin):
inlines = (PropertyTabularInline,)
How can I easily do what I want?
You can overwrite the User admin page to display both the Profile and the Property models.
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from myapp.models import *
class ProfileInline(admin.TabularInline):
model = Profile
class PropertyInline(admin.TabularInline):
model = Property
class UserAdmin(UserAdmin):
inlines = (ProfileInline, PropertyInline,)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
You can also remove any unwanted/unused User properties from being displayed (e.g. Groups or Permissions)
more here: https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#extending-the-existing-user-model
and here:
https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#a-full-example
class PropertyTabularInline(admin.TabularInline):
model = Property
def formfield_for_dbfield(self, field, **kwargs):
if field.name == 'user':
# implement your method to get userprofile object from request here.
user_profile = self.get_object(kwargs['request'], UserProfile)
kwargs["queryset"] = Property.objects.filter(user=user_profile)
return super(PropertyInLine, self).formfield_for_dbfield(field, **kwargs)
once this is done, you can add this inline to user UserProfileAdmin like:
class UserProfileAdmin(admin.ModelAdmin):
inlines = (PropertyTabularInline,)
Haven't tested it, but that should work.
It is achievable by making one change in your models.
Instead of creating OneToOne relationship from UserProfile to User, subclass User creating UserProfile. Code should look like that:
class UserProfile(User):
# some other fields, no relation to User model
class Property(models.Model):
user = models.ForeignKey(User)
That will result in creating UserProfile model that have hidden OneToOne relation to User model, it won't duplicate user model.
After doing that change, your code will work. There are some changes under the hood, like UserProfile no longer have it's own ID, you can access fields from User inside UserProfile and it's hard to swap User model using settings.AUTH_USER_MODEL (that will require creating some custom function returning proper type and changing migration by hand) but if this is not a problem for you, it may be good solution.
Let's say I have a model as follows.
models.py
class Profile(models.Model):
user = models.OneToOneField(User)
middle_name = models.CharField(max_length=30, blank=True, null=True)
And I have a custom field email as follows in a ModelForm
forms.py
class ProfileForm(ModelForm):
email = forms.CharField()
class Meta:
model = models.Profile
fields = ('email', 'middle_name')
In the am setting an instance of the above mentioned modelform so the data is prefilled in the form for an edit template as follows.
views.py
def edit_profile(request):
profile = models.Profile.objects.get(user=request.user)
profileform = forms.ProfileForm(instance=profile)
return render_to_response('edit.html', { 'form' : 'profileform' }, context_instance=RequestContext(request))
Now in the form I get all the values prefilled for all the fields under Profile model but the custom fields are empty and it makes sense.
but is there a way I can prefill the value of the custom fields ? maybe something like:
email = forms.CharField(value = models.Profile.user.email)
Can I suggest something else? I'm not a huge fan of having that email field within a ModelForm of Profile if it has nothing to do with that model.
Instead, how about just having two forms and passing in initial data to your custom one containing email? So things would look like this:
forms.py
# this name may not fit your needs if you have more fields, but you get the idea
class UserEmailForm(forms.Form):
email = forms.CharField()
views.py
profile = models.Profile.objects.get(user=request.user)
profileform = forms.ProfileForm(instance=profile)
user_emailform = forms.UserEmailForm(initial={'email': profile.user.email})
Then, you're validating both the profile and user email form, but otherwise things are mostly the same.
I assume you are not sharing logic between the Profile ModelForm and this UserEmailForm. If you need profile instance data, you could always pass that in there.
I prefer this approach because it's less magical and if you look back at your code in a year, you won't be wondering why, in brief scanning, why email is part of the ModelForm when it does not exist as a field on that model.
I have a model named Project which has a m2m field users. I have a task model with a FK project. And it has a field assigned_to. How can i limit the choices of assigned_to to only the users of the current project?
You could do this another way, using this nifty form factory trick.
def make_task_form(project):
class _TaskForm(forms.Form):
assigned_to = forms.ModelChoiceField(
queryset=User.objects.filter(user__project=project))
class Meta:
model = Task
return _TaskForm
Then from your view code you can do something like this:
project = Project.objects.get(id=234)
form_class = make_task_form(project)
...
form = form_class(request.POST)
You need to create a custom form for the admin.
Your form should contain a ModelChoiceField in which you can specify a queryset parameter that defines what the available choices are. This form can be a ModelForm.
(the following example assumes users have an FK to your Project model)
forms.py
from django import forms
class TaskForm(forms.ModelForm):
assigned_to = forms.ModelChoiceField(queryset=Users.objects.filter(user__project=project))
class Meta:
model = Task
Then assign the form to the ModelAdmin.
admin.py
from django.contrib import admin
from models import Task
from forms import TaskForm
class TaskAdmin(admin.ModelAdmin):
form = TaskForm
admin.site.register(Task, TaskAdmin)