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

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.

Related

Save the data of current logged user in django

I am a newbie in django and I have a question about how I can save and show only the data of logged user - since my application is multi-tenant.
my view
class ProjetoCreate(CreateView):
model = Projeto
fields = ['nomeProjeto',
'descricao',
'dtInicio',
'deadline',
'nomeSprint',
'status',
]
def get_queryset(self):
logged_user = self.request.user
return Projeto.objects.filter(User=logged_user)
class ProjetoList(ListView):
paginate_by = 2
model = Projeto
my model
class Projeto(models.Model):
nomeProjeto = models.CharField(max_length=20)
descricao = HTMLField()
dtInicio = models.DateField(auto_now=False, auto_now_add=False)
deadline = models.DateField(auto_now=False, auto_now_add=False)
nomeSprint = models.CharField(max_length=30)
status = models.CharField(max_length=20)
Thank you very much!
Add
user = models.ForeignKey(User, on_delete=models.CASCADE)
to Projecto model. Then, in your view, set project.user = self.request.user before saving your project model.
I think you are doing it completely wrong.
You shouldn't be using get_queryset() at all in CreateView - https://stackoverflow.com/a/24043478/4626254
Here's is what you can try instead.
Add a user field in Project model and apply migrations.
user = models.ForeignKey(User, on_delete=models.CASCADE)
Create a class inheriting Generic APIView instead of CreateView.
Create a POST method like def post(self, request): inside that class and get all the details for creating a Projeto object in the request payload using request.data or request.POST.
Get the logged in user using request.user
Create a Projecto object with all this information as Projeto.objects.create(**your_other_fields, user=request.user)
Next time when filtering the objects, use a filter on user field like user=request.user.

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

How to set dynamic initial values to django modelform field

I'm kinda new to django, I need to set a dynamic initial value to my modelform field. I have a database field in my model name 'author' it has a foreignkey that connects it to the django user model. I need to automatically set this to the current user anytime a user fills in information into the form.
from what I gathered about this problem, I'd have to define an __init__ function inside the MyHouseEditForm below, I'm new to django and all the examples I've seen a pretty confusing.
forms.py
from django import forms
from django.contrib.auth.models import User
from .models import Myhouses
class MyHouseEditForm(forms.ModelForm):
class Meta:
model = Myhouses
fields = ('author','name_of_accomodation', 'type_of_room', 'house_rent', 'availability', 'location', 'nearest_institution', 'description', 'image')
i need to set the value of 'author' to the current user anytime a user logs in.
models.py
from django.db import models
from django.contrib.auth.models import User
class Myhouses(models.Model):
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='author')
Available = 'A'
Not_Available = 'NA'
Availability = (
(Available, 'Available'),
(Not_Available, 'Not_Available'),
)
name_of_accomodation = models.CharField(max_length=200)
type_of_room = models.CharField(max_length=200)
house_rent = models.IntegerField(null=True)
availability = models.CharField(max_length=2, choices=Availability, default=Available,)
location = models.CharField(max_length=200)
nearest_institution = models.CharField(max_length=200)
description = models.TextField(blank=True)
image = models.ImageField(upload_to='profile_image')
def __str__(self):
return self.name_of_accomodation
views.py
#login_required
def addlisting(request):
if request.method == 'POST':
form = MyHouseEditForm(request.POST, files=request.FILES)
if form.is_valid():
Houses = form.save(commit=False)
Houses.save()
return redirect('addlisting')
else:
form = MyHouseEditForm()
return render(request, 'houses/addlisting.html', {'form':form })
No need to show author field in form. It would automatically populate with logged in user.
request.user gives you logged in user object. So, you may remove 'author' filed from forms field section and do this:
Houses = form.save(commit=False)
Houses.author = request.user
Houses.save()
I did something like this in the serializer.
I defined a custom create method like this:
class MyhousesSerializer(FlexFieldsModelSerializer):
...
def create(self, validated_data):
validated_data['author'] = self.context['request'].user
newhouse = Myhouses.objects.create(**validated_data)
return newhouse
It shouldn't matter if you use a more regular model serializer.

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.

Categories

Resources