Can't update User and UserProfile in one View? - python

I use UpdateView to update user account. User consists of User and UserProfile like this:
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE,related_name='userprofile')
telephone = models.CharField(max_length=40,null=True)
Now, I've created a class UpdateView to be able to update for example UserProfile - telephone which works.
FORM:
class UserProfileUpdateForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('telephone',)
URLS:
url(r'^edit-profile$',view=views.UserUpdate.as_view(),name='user_update'),
VIEW:
# #login_required
class UserUpdate(UpdateView):
form_class = UserProfileUpdateForm
context_object_name = 'user_update'
template_name = 'auth/profiles/edit-profile.html'
success_url = 'success url'
def get_object(self,queryset=None):
return self.request.user.userprofile
def form_valid(self, form):
#save cleaned post data
clean = form.cleaned_data
self.object = form.save()
return super(UserUpdate, self).form_valid(form)
Now, I want to be able to change some attributes which belongs to User and some attributes which belongs to UserProfile.
I've already tried to change UserProfileUpdateForm fields variable but It does not work at all...
class UserProfileUpdateForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('telephone','model.user.first_name',) <- this does not work, I want to add to the form attribute 'first_name' which belongs to User, not UserProfile
Do you know what to do to be able to change telephone, first_name, last_name etc. using UpdateView?

UpdateView is only made to handle one model with no relations. However, the wonderful django-extra-views library provides CBVs for models and inline relations.
class UserProfileInline(InlineFormSet):
model = models.UserProfile
form = UserProfileUpdateForm
extra = 0
def get_factory_kwargs(self):
kwargs = super(UserProfileInline,self).get_factory_kwargs()
kwargs.update({"min_num": 1})
return kwargs
class UserCreate(CreateWithInlinesView):
model=User
inlines = [UserProfileInline]
form_class = UserForm
success_url = reverse('some-success-url')
# REQUIRED - fields or exclude fields of User model
template_name = 'your_app/user_profile_update.html'
Be sure to check out the documentation for information on the variables passed to your template and how to work with inline formsets.

You have to create second form for User as well. Then pass it to the same UpdateView as a second form_class.
Note*: you may need to override get and post methods for UpdateView. This SO answer might help.
Render both forms in one template under one <form> tag:
<form action="" method="post">
{% csrf_token %}
{{ first_form }}
{{ second_form }}
</form>

Related

Allow user to edit his own 'profile model', which has a OneToOne relationship with the user model

I am using the default User model along with a custom AgentBasicInfo 'profile model' with additional information about the user.
I am trying to give each user the ability to edit his profile model and only his own. I am using the generic.edit UpdateView.
I am confused as to how I approach this. I have tried a few things but gotten errors, mainly NoReverseMatch. See my code below:
views.py
class EditBasicInfo(LoginRequiredMixin, UpdateView):
model = AgentBasicInfo
form_class = AgentBasicInfoForm
# the user want to edit this post must be owner this post
def get_queryset(self):
post_qs = super(EditBasicInfo, self).get_queryset()
return post_qs.filter(user=self.request.user)
models.py
class AgentBasicInfo(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
user_first_name = models.CharField(max_length=30)
user_last_name = models.CharField(max_length=30)
preferred_email = models.EmailField()
office_phone_number = models.CharField(max_length=10)
brokerage_of_agent = models.CharField(max_length=50)
def __str__(self):
return self.user_last_name
urls.py
url(r'^edit_base_info/(?P<pk>\d+)/$', views.EditBasicInfo.as_view(), name='edit_base_info'),
HTML URL tag
{% url 'edit_base_info' agentbasicinfo.id %}
I need some guidance as to how I can achieve this. Thank you!
Updated views.py
class EditBasicInfo(LoginRequiredMixin, UpdateView):
model = AgentBasicInfo
form_class = AgentBasicInfoForm
# the user want to edit this post must be owner this post
def get_object(self):
return self.get_queryset().get(user=self.request.user)
If the user should only be able to edit their own profile, you can use the same url for everyone.
url(r'^edit_base_info/$', views.EditBasicInfo.as_view(), name='edit_base_info')
Then you don't need to pass extra arguments to {% url %}.
(I'm assuming you are not using namespaced urls)
{% url 'edit_base_info' %}
Instead of overriding get_queryset, you should override get_object to get the specific profile instance.
get_object(self):
return self.get_queryset().get(user=self.request.user)

Django: Set initial value for ForiegnKey in a CreateView

I'm trying to create a view of type CreateView. This view will take the form_class = CourseForm that I created and excluded some fields in it. The instructor field is a foriegnKey and I don't want the user to be able to control it in the form. It's a field that depends on the signed in user.
# forms.py
class CourseForm(forms.ModelForm):
class Meta:
model = Course
exclude = ['instructor', 'members', 'slug']
# ...
my view is as follows. I thought that by including the instructor value in initial would pass the profile instance when I submit
# views.py
#method_decorator(login_required, name='dispatch')
class CourseCreateView(CreateView):
model = Course
template_name = 'course_form.html'
success_url = reverse_lazy('course-create-complete')
form_class = CourseForm
def get_initial(self):
initial = super(CourseCreateView, self).get_initial()
initial = initial.copy()
profile = get_object_or_404(Profile, user__username=self.request.user)
initial['instructor'] = profile
return initial
# models.py
class Course(models.Model):
instructor = models.ForeignKey(Profile, related_name="Instructor")
# ... other fields
but the probelm is that whenever I submit the form I get the following error:
NOT NULL constraint failed: myapp_course.instructor_id
If you want to set the initial value of instructor field, you shouldn't exclude it from the form. You could instead make the field hidden.
Or you could include that in the exclude list, but then you shouldn't override get_initial method, but do the assignment manually:
class CourseCreateView(CreateView):
def form_valid(self, form):
self.object = form.save(commit=False)
# create instructor based on self.request.user
self.object.instructor = instructor
self.object.save()
return HttpResponseRedirect(self.get_success_url())
Check django doc about what does save(commit=False) do.
Also check django doc about form_valid function and how forms are handled in class based views.

Attach User to Django ModelForm / FormView after submit

I have a FormView CBV (shows a ModelForm) that is working fine. However, I now want to append a "created_by" attribute to the form, upon saving it to the database. This "created_by" field should be the current, logged-in user, who has filled out the form.
I have tried delaying form.save(), appending the request.user and then saving everything...but the page just redirects to itself, and the model data isn't added. Thoughts?
Relevant models.py:
class Event(models.Model):
title = models.CharField(max_length=255)
submitted_date = models.DateField(auto_now=True, verbose_name='Date Submitted')
created_by = models.ForeignKey(User)
event_date = models.DateField()
Relevant views.py:
class PostEventView(FormView):
form_class = EventForm
template_name = "event-submit.html"
def form_valid(self, form):
form = form.save(commit=False)
form.created_by = self.request.user
form.save()
messages.success(self.request, 'Your event was submitted successfully. Thank you for taking the time to add this opportunity!')
return HttpResponseRedirect(reverse_lazy('single_event', kwargs={'slug': form.slug}))
Thoughts?
Credit to #Daniel Roseman for pointing out that I had forgotten to exclude = ['created_by'] from my ModelForm. Adding this field was an afterthought, and I had consequently forgotten to exclude it. My template was also missing {{ form.non_field_errors }}, which explained why I wasn't seeing any validation errors.

Passing instance/default value to a ModelFormSet for the empty forms to use, in a view

How do i pre-populate the exclude fields in a ModelFormSet. AuthorFormSet below doesn't take an instance argument here, only queryset. So how do i force the empty forms coming in not to have a NULL/None user attribute. I want to be able to set that user field to request.user
models.py
class Author(models.Model):
user = models.ForeignKey(User, related_name='author')
# assume some other fields here, but not relevant to the discussion.
forms.py
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
class BaseAuthorFormSet(BaseModelFormSet):
"""will do some clean() here"""
pass
AuthorFormSet = modelformset_factory(Author,\
form=AuthorForm, \
formset=BaseAuthorFormSet, extra=1,\
exclude=('user',)
)
views.py
def my_view(request):
if request.method == 'POST':
formset = AuthorFormSet(request.POST)
if form.is_valid():
form.save() # This will cause problems
# for the empty form i added,
# since user can't be null.
EDIT
This is what i ended up doing in my view. Wogan's answer is partly correct, i think it's just missing the iterating over all the forms part.
for form in formset.forms:
form.instance.user = request.user
formset.save()
You can exclude fields in the model form, these will then be excluded from the formset created by modelformset_factory.
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
exclude = ('user',)
In your view, pass commit=False to the form's save() method, then set the user field manually:
def my_view(request):
if request.method == 'POST':
formset = AuthorFormSet(request.POST)
if formset.is_valid():
for author in formset.save(commit=False):
author.user = request.user
author.save()
formset.save_m2m() # if your model has many to many relationships you need to call this

django model Form. Include fields from related models

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

Categories

Resources