Attach User to Django ModelForm / FormView after submit - python

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.

Related

Can I somehow call logic from form_valid() in Django tests?

I am trying to test form for Post model creation in my simple forum application. The problem I am having is that after I try to save the form in tests I get an error NOT NULL constraint failed: forum_web_post.user_id because I am assigning the user in the form_valid() method in the view. The user is not passed via the form since the user that creates the post is the signed in user that sent the request.
models.py
class Post(models.Model):
category = models.ForeignKey(Category, on_delete=models.PROTECT)
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
text = models.TextField()
created_at = models.DateTimeField(auto_now=True)
user is imported form django.contrib.auth.models and Category model looks like this.
class Category(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now=True)
in views.py after the user submits the form he is the redirected to his profile page
views.py
class PostCreate(generic.CreateView):
model = Post
form_class = PostForm
template_name = 'forum_web/post_edit.html'
def form_valid(self, form):
post = form.save(commit=False)
post.user = models.User.objects.get(id=self.request.user.id)
post.save()
return HttpResponseRedirect(reverse_lazy('forum:user_detail', kwargs={'pk': self.request.user.id}))
forms.py
class PostForm(ModelForm):
class Meta:
model = Post
fields = ['category', 'title', 'text']
tests.py
def test_post_form_create_should_pass(self):
# this dict is in setUp() as well as the test_category but for simplicity I will include it in this method
post_data = {
'category': self.test_category.pk,
'title': 'test_post',
'text': 'test_post_text'
}
post_count = Post.objects.count()
form = PostForm(data=self.post_data)
self.assertTrue(form.is_valid())
form.save()
self.assertEqual(post_count + 1, Post.objects.count())
any help would be appreciated!
You are just testing the form. So it doesn't make sense to call a view function in your test. You should manually assign a user to the form's instance using form.instance.user = ... before saving the form in your test, since that's the way the form should be used.
In addition, you should test your view by actually logging in a user and posting the request to the PostCreate view. Here you're testing that the view correctly saves the form (and that your form_valid() method works correctly).
Note: post.user = self.request.user would be better than re-fetching the user from the database using the id. It's redundant.

Django: CreateView don't show field and set it to request.user (ForeignKey)

I use a generic CreateView to let logged in users (Creator) add objects of the model Piece. Since creating a Piece is done by the Creator (logged in user) there is no need for the CreateView to either show or manipulate the 'creator' field. Hence I wish to not show it and set it to the logged in user. However, approaches such as overwriting form_valid or using get_form_kwargs seem not to get it done. Using the form_valid method gives a ValueError:
Cannot assign "<SimpleLazyObject: <User: patrick1>>": "Piece.creator" must be a "Creator" instance.
The solution seems to be just around the corner, I hope.
Tried but did not work:
form_valid method, form_valid method, get_form_kwargs method
My code:
models.py
class Piece(models.Model):
creator = models.ForeignKey('Creator', on_delete=models.SET_NULL, null=True)
title = models.CharField(max_length=200)
summary = models.TextField(max_length=1000, help_text='Enter a brief description of the Piece')
created = models.DateField(null=True, blank=True)
...
from django.contrib.auth.models import User
class Creator(models.Model):
...
user = models.OneToOneField(User, on_delete=models.CASCADE)
...
views.py
class PieceCreate(LoginRequiredMixin, CreateView):
model = Piece
fields = ['title', 'summary', 'created']
initial = {'created': datetime.date.today()}
def form_valid(self, form):
obj = form.save(commit=False)
obj.creator = self.request.user
return super(PieceCreate, self).form_valid(form)
success_url = reverse_lazy('pieces')
Any suggestions are highly appreciated!
obj.creator = Creator.objects.get(user=self.request.user)
or any other solution that will give you Creator instance for current user instead of User. Just as the error message says.
Cannot assign "User: patrick1": "Piece.creator" must be a "Creator" instance.

(Django) Model of particular person with this User already exists

Dear StackOverFlow community,
Basing on a built-in user User model I've created my own model class called "ModelOfParticularPerson". The structure of it looks like this:
class ModelOfParticularPerson(models.Model):
user = models.OneToOneField(User)
nickname = models.CharField(max_length=100, null=True, unique=False)
uploaded_at = models.DateTimeField(auto_now_add=True, null=True)
email_address = models.EmailField(max_length=200, blank=False, null=False, help_text='Required')
description = models.CharField(max_length=4000, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True, blank=True)
Unfortunately, after loggin in with the usage of particular account, whenever I am trying to reedit the profile, I do get following error:
"Model of particular person with this User already exists."
Any advice is priceless.
Thanks.
ps.
views.py:
[..]
#method_decorator(login_required, name='dispatch')
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
model = ModelOfParticularPerson
form_class = ModelOfParticularPersonForm
success_url = "/accounts/profile/" # You should be using reverse here
def get_object(self):
# get_object_or_404
return ModelOfParticularPerson.objects.get(user=self.request.user)
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
def post(self, request):
form = ModelOfParticularPersonForm(self.request.POST, self.request.FILES)
if form.is_valid():
print("FORM NOT VALID!")
profile = form.save(commit=False)
profile.user = self.request.user
profile.save()
return JsonResponse(profile)
else:
return render_to_response('my_account.html', {'form': form})
urls.py:
urlpatterns = [
[..]
url(r'^login/$', auth_views.LoginView.as_view(template_name='login.html'), name='login'),
url(r'^accounts/profile/$', ProfileUpdateView.as_view(template_name='my_account.html'), name='my_account'),
]
forms.py
class ModelOfParticularPersonForm(ModelForm):
class Meta:
model = ModelOfParticularPerson
fields = '__all__'
widgets = {
'user':forms.HiddenInput(),
'uploaded_at':forms.HiddenInput(),
'created':forms.HiddenInput(),
}
You need to pass the instance to the form, otherwise Django will try to create a new object when you save it.
def post(self, request):
form = ModelOfParticularPersonForm(instance=self.get_object(), self.request.POST, self.request.FILES)
...
You should try to avoid overriding get or post when you're using generic class based views. You can end up losing functionality or having to duplicate code. In this case, it looks like you can remove your post method. In the form_valid method you can return a JsonResponse. You shouldn't have to set form.instance.user if you are updating an existing object.
def form_valid(self, form):
profile = form.save()
return JsonResponse(profile)
Finally, you should leave fields like user and uploaded_at out of the model form instead of making them hidden fields.
You're creating new forum in your post method of view, but you're not passing existing model object to it. That leads to creation of new model, which fails, because object already exists.
Instead of overwritting post method, put saving of object inside is_valid method and use already provided form object (passed to you by method parameter).

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)

Can't update User and UserProfile in one View?

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>

Categories

Resources