UpdateView - Update a class that has OneToOne with User - django

Say I have a model called MyUser. It has some field, and one of them is this one:
user = OneToOneField(User, related_name='more_user_information')
I want to make a view to update this model, and I do the following:
Class AccountEdit(LoginRequiredMixin, UpdateView):
model = MyUser
form_class = MyUserForm
template_name = 'accounts/edit.html'
def get_object(self, queryset=None):
return self.model.objects.get(user=self.request.user)
Each field in MyUser renders fine for editing, except user. This one to one field becomse a select drop down box. What I like to do is to edit the fields on User model like first name or last name.
How can I achieve this while extending UpdateView? or perhaps shuold I use a FormView?
thanks

This problem is actually nothing to do with class based views or update view - its a basic issue that has been there since the beginning, which is:
ModelForms only edit the fields for one model, and don't recurse into
foreign keys.
In other words, if you have a model like this:
class MyModel(models.Model):
a = models.ForeignKey('Foo')
b = models.ForeignKey('Bar')
c = models.ForeignKey('Zoo')
name = models.CharField(max_length=200)
A model form will render three select fields, one for each foreign key, and these select fields will have all the values from those models listed - along with one text field for the name.
To solve this problem, you need to use InlineFormSets:
Inline formsets is a small abstraction layer on top of model formsets.
These simplify the case of working with related objects via a foreign
key.
You should use InlineFormSet from the excellent django-extra-views app. To do this, you'll create a view for the related object as well:
class MyUserInline(InlineFormSet):
model = MyUser
def get_object(self):
return MyUser.objects.get(user=self.request.user)
class AccountEditView(UpdateWithInlinesView):
model = User
inlines = [MyUserInline]

Another option is django-betterforms's Multiform and ModelMultiForm.
Example:
class UserProfileMultiForm(MultiForm):
form_classes = {
'user': UserForm,
'profile': ProfileForm,
}
It works with generic CBV (CreateView, UpdateView, WizardView).

Related

How can I access parent model fields in a form using the child model?

For context, I'm trying to create a form that allows users to upload info about their own custom Pokemon. Basically, they are creatures that you can catch, name, and level up. To draw a comparison, it is a similar concept to dogs; there are labradors, German Shepherds, huskies, etc. that would be variations of a base Dog model, but then each individual would have a name and other defining characteristics.
I've created Pokemon and CustomPokemon models and imported the latter into my forms.py file. I'm trying to access some parent fields but am unable to:
from django import forms
from .models import CustomPokemon
class PokemonForm(forms.ModelForm):
class Meta:
model = CustomPokemon
fields = ['pokemon.poke_name', 'name', 'level']
The poke_name field is inherited from the parent Pokemon model while the other two fields belong to the CustomPokemon model. I'm getting this FieldError:
Unknown field(s) (pokemon.poke_name) specified for CustomPokemon.
The issue isn't resolved by using poke_name, so I'm curious how I can access the parent model's fields so they can be displayed in the form.
First option
If you just want a dropdown displaying the field poke_name, what you could do is to define a __str__ method inside Pokemon model like this:
class Pokemon(model.Model):
...
def __str__(self):
return self.poke_name
Then, you can define the form as follows:
class PokemonForm(forms.ModelForm):
class Meta:
model = CustomPokemon
fields = ['pokemon', 'name', 'level']
And you will get a dropdown displaying all the poke_name of your database, where you can choose your foreign key. It would be better if poke_name is a unique field so that the foreign key can be clearly identified.
Second option
If you need more freedom, you could manually define a custom field as follows:
class PokemonForm(forms.ModelForm):
poke_name = forms.CharField()
class Meta:
model = CustomPokemon
fields = ['name', 'level']
Then, when validating the form, you should take care of whether the entry exists and/or create it:
if form.is_valid():
form.instance.pokemon = Pokemon.objects.get_or_create(cname=form.cleaned_data['poke_name'])
form.save()

How to populate django form with selection from your models

I have a CreateSong CBV in Django that allows me to create song objects to a model. My question is, in the form I created for the view, how do I make the album column to be auto-populated with albums the user-created only? I get errors calling "self" that way.
See my views below
class CreateSong(CreateView):
model = Song
fields = [album, song_title]
fields['album'].queryset = Album.objects.filter(owner=self.request.user)
I think you should override get_form. See the example below:
class CreateSong(CreateView):
model = Song
fields = [album, song_title]
def get_form(self):
form = super().get_form()
form.fields['album'].queryset = Album.objects.filter(owner=self.request.user)
return form
You do not have access to self.request.user, because you are calling it at class level, thus when the class is being defined and not when the view is actually called. Instead you should override the get_form method as in Davit's answer.

Django admin: don't send all options for a field?

One of my Django admin "edit object" pages started loading very slowly because of a ForeignKey on another object there that has a lot of instances. Is there a way I could tell Django to render the field, but not send any options, because I'm going to pull them via AJAX based on a choice in another SelectBox?
You can set the queryset of that ModelChoiceField to empty in your ModelForm.
class MyAdminForm(forms.ModelForm):
def __init__(self):
self.fields['MY_MODEL_CHOIE_FIELD'].queryset = RelatedModel.objects.empty()
class Meta:
model = MyModel
fields = [...]
I think you can try raw_id_fields
By default, Django’s admin uses a select-box interface () for fields that are ForeignKey. Sometimes you don’t want to incur the overhead of having to select all the related instances to display in the drop-down.
raw_id_fields is a list of fields you would like to change into an Input widget for either a ForeignKey or ManyToManyField
Or you need to create a custom admin form
MY_CHOICES = (
('', '---------'),
)
class MyAdminForm(forms.ModelForm):
my_field = forms.ChoiceField(choices=MY_CHOICES)
class Meta:
model = MyModel
fields = [...]
class MyAdmin(admin.ModelAdmin):
form = MyAdminForm
Neither of the other answers worked for me, so I read Django's internals and tried on my own:
class EmptySelectWidget(Select):
"""
A class that behaves like Select from django.forms.widgets, but doesn't
display any options other than the empty and selected ones. The remaining
ones can be pulled via AJAX in order to perform chaining and save
bandwidth and time on page generation.
To use it, specify the widget as described here in "Overriding the
default fields":
https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/
This class is related to the following StackOverflow problem:
> One of my Django admin "edit object" pages started loading very slowly
> because of a ForeignKey on another object there that has a lot of
> instances. Is there a way I could tell Django to render the field, but
> not send any options, because I'm going to pull them via AJAX based on
> a choice in another SelectBox?
Source: http://stackoverflow.com/q/37327422/1091116
"""
def render_options(self, *args, **kwargs):
# copy the choices so that we don't risk affecting validation by
# references (I hadn't checked if this works without this trick)
choices_copy = self.choices
self.choices = [('', '---------'), ]
ret = super(EmptySelectWidget, self).render_options(*args, **kwargs)
self.choices = choices_copy
return ret

Django forms and registration: insert data to other tables

In my app, I've got 2 types of users: Student and Teacher. I want to store additonal information about each of them in separate table.
class Student(models.Model):
user = models.ForeignKey(User)
nr_indeksu = models.BigIntegerField()
def __unicode__(self):
return unicode(self.user)
Let's assume i want to register user as student - In that case, i need to expand User registration form, so it will contain fields from Group and Student model, and during registration i want to insert Student.nr_indeksu with Student.user_id(FK) into student table, and Group.name into User_Groups.
I've created StudentForm by using Meta Class and rendered it in template in the same form where UserCreationForm is, but i don't know how to handle saving into database, because UserCreationForm creates user_id that it's supposed to go into Student mode. Any pointers?
I had a similar issue. Solved it by making the model look a little different:
class Student(User):
nr_indeksu = models.BigIntegerField()
def __unicode__(self):
return unicode(self.user)
This way, when you define the Django form, all needed fields will be rendered for this model. This is because you are saying The student is a user , as opossed to the former the student has a user
The form could look like:
class StudentForm(forms.ModelForm):
class Meta:
model = Student
Edit:
Use a similar approach for teacher model.
If you want to avoid the rendering of some fields, use the fields attribute in the Meta class.
Second Edit:
If you want to preserve the validations and checks of the User form, the form should also inherit from UserCreationForm.

Creating a ModelForm where my primary key field generates a select box

I am not sure if my language clear enough but basically I have this form:
class Paper(models.Model):
number = models.CharField(max_length=12,primary_key=True)
project = models.ForeignKey(Project)
class SomeForm(forms.ModelForm):
class Meta:
model = Paper
fields = ('project', 'number')
and django creates a textfield for me. What I want is a select box with the existing primary keys.
Thanks.
I don't think you want a modelform - that's for creating and updating model instances. It sounds like you just want a form to select IDs. Use a normal form, but with a ModelChoiceField.
class SomeForm(forms.Form):
ids = forms.ModelChoiceField(queryset=Paper.objects.all())
You'll need to give the Paper model a __unicode__ method that returns self.number.