Add data to a form in `CreateView` - django

I have a model which the user submits on a form, and I would like to handle that form with a CreateView. However, there is one field in the model which the user doesn't provide, which is their IP address.
The problem is that the CreateView fails with an IntegrityError since the field is empty. I tried modifying request.POST to add the relevant field, but that's not allowed (and a bad idea).
I figured I could use a hidden input on the form and put the IP there but that means the user can blank it or modify it if they like, I want the exact IP that did the POST request.
If I understand correctly both the form_valid and form_invalid methods are too early in the process, since the object hasn't been created yet? Is there any other way of doing this?
Here is what the code looks like:
class Answer(models.Model):
ip_address = models.GenericIPAddressField()
text = models.TextField()
and the view:
class AnswerForm(CreateView):
template_name = "answer.html"
model = Answer
success_url = reverse_lazy('answer')
fields = ['text']

The request data is stored in the object itself, therefore you can access it as such:
class AnswerForm(CreateView):
template_name = "answer.html"
model = Answer
success_url = reverse_lazy('answer')
fields = ['text']
def form_valid(self, form):
form.instance.ip_address = get_ip_address(self.request)
form.save()
return super().form_valid(form)

Related

Django - modelform + model property

I am trying to solve one issue about saving data in db.
This is an example how I think of it:
class MyModel(models.Model):
id = models.AutoField(primary_key=True)
fieldX = models.SomeFieldType()
#property:
def foo(self):
return self._foo
#foo.setter
def foo(self, var):
self._foo=var
class MyModelForm(models.Modelform):
class Meta:
model = models.MyModel
fields = '__all__'
The thing is I have dict that I am passing to this form (so I am not using view or any provided interaction with user directly. In dict I have some fields and what I want to do is one field that is passed to use it as model property but I do not want it to be saved in db.
So I tried something like:
form = MyModelForm(data_dict)
if form.is_valid():
form.foo = data_dict['data_for_property_not_db']
form.save()
Form does not know that my model has this property.
So basiclly what I want is to write some parts of my data_dict normaly to form and db as always ->works fine
and then I want some data_info pass to that property and use it somewhere in save() as needed without saving it to db itself.
Can you help me with this?

Django validate answer for specific question

Overview:
I want to build a Question-Answer website, where the user has to enter the correct answer for each question. I have made 3 models for this:
class ProblemSet(models.Model):
id = models.IntegerField(primary_key=True)
class Problem(models.Model):
id = models.IntegerField(primary_key=True)
problem_set = models.ForeignKey(ProblemSet, on_delete=models.CASCADE)
question = models.TextField()
solution = models.TextField()
class Solve(models.Model):
username = models.ForeignKey(User, on_delete=models.CASCADE)
problem_set = models.ForeignKey(ProblemSet, on_delete=models.CASCADE)
problem_id = models.ForeignKey(Problem, on_delete= models.CASCADE)
In solve model, if there is any entry that means that particular user has solved that problem_id.
So, I have utilized the generic Form View:
class IndexView(FormView):
form_class = ProblemForm
template_name = 'home/index.html'
success_url = reverse_lazy('index')
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated:
inner_qs = "fetch ids that are solved from Solve model"
problem_obj = Problem.objects\
.exclude(id__in=inner_qs)\
.order_by('id').first()
else:
#do something
context['question'] = problem_obj.question
return context
The problem form is:
from django import forms
class ProblemForm(forms.Form):
solution = forms.CharField(widget=forms.TextInput())
How do I validate that the user is inputting the correct answer? I do get the value of solution field in def form_valid(self, form) function but how should I deal with it? Should i pass question_id in context and query the database in form_valid, or should i pass solution itself to context and access context data in form_valid() method to prevent double query but in this method I am not sure if this is secure as I don't want solution to be passed to client.
Is there any elegant way of doing this?
P.S. After user entered solution is compared to the one in database for that question, I add an entry in the Solve table denoting that this particular user has solved the question id.
The FormView is processing two separate requests: First the GET request when the student fetches the form with a question to answer. Then the POST request when the student submits her answer to the question.
Now HTTP is stateless so somehow you need to keep track of the question that was presented in the first request so you know which question was answered when receiving the POST request.
The easiest way I would say is to actually include the question_id in the form itself, as a hidden input field. There's not real security issue here: The question_id can be manipulated by the student even though it's hidden, but what's the point?
So this is what I would do:
Add problem as a ModelChoiceField with a HiddenInput widget to your ProblemForm.
problem = forms.ModelChoiceField(queryset=Problem.objects.all(), widget=forms.HiddenInput())
Set an initial value for the problem in the get_inital() method of your IndexView:
def get_problem(self): # use also in get_context_data() to add the question
if hasattr(self, 'problem'):
return self.problem
if self.request.user.is_authenticated:
inner_qs = "fetch ids that are solved from Solve model"
self.problem = Problem.objects\
.exclude(id__in=inner_qs)\
.order_by('id').first()
return self.problem
def get_initial(self):
initial = super().get_initial()
initial['problem'] = self.get_problem()}
return initial
When the form gets submitted and is valid, you'll see that form.cleaned_data['problem'] is the submitted problem. So you can use that in the form_valid() method:
def form_valid(self, form):
problem = form.cleaned_data['problem']
# check that it hasn't been solved by the user already
if problem.answer == form.cleaned_data['solution']:
# create solve object for the user
return redirect(...)
The alternative would be to not include it in the form but refetch the problem in form_valid (note that the problem is fetched in the above method as well, when the form maps the submitted problem_id to the actual problem instance to populate its cleaned_data).

How to access new data in Django UpdateView

I wanted to display in success_message new data, changed in my generic UpdateView.
And i dunno how to access those data from form.
Everything is like super standard, code look like that:
class ProductOneUpdateView(UpdateView):
model = ProductOne
fields = ['quantity']
success_message = ................
And i want new quantity, changed by user shown in this success_message.
Looking forward for yours answers!
As the documentation says:
The cleaned data from the form is available for string interpolation using the %(field_name)s syntax.
So if these are form fields, you can write a message like:
class ProductOneUpdateView(SuccessMessageMixin, UpdateView):
model = ProductOne
fields = ['quantity']
success_message = 'The quantity is updated to %(quantity)s'
For more advanced processing of success messages, you can override the get_success_message method [Django-doc], this passes the cleaned_data as parameter, and you can access the (updated) object through self.object:
class ProductOneUpdateView(SuccessMessageMixin, UpdateView):
model = ProductOne
fields = ['quantity']
def get_success_message(self, cleaned_data):
return 'The quantity of {} is updated to {}'.format(
self.object,
self.cleaned_data['quantity']
)

How can I achieve one single form, pre-populated with data from two different Profile instances?

I have a model, Pair, and another model Profile.
An instance of the model Pair will be made with a form that draws from two different Profile instances. So, how do I pre-populate a single form with bits of information from two Profiles?
Two models: Profile & Pair:
class Profile(models.Model):
...
favorites = models.CharField(max_length=150)
class Pair(models.Model):
requester = models.ForeignKey(Profile)
accepter = models.ForeignKey(Profile)
requester_favorite = models.CharField(max_length=50)
accepter_favorite = models.CharField(max_length=50)
The form so far:
class PairRequestForm(forms.Form):
your_favorites = forms.CharField(max_length=50)
partners_favorites = forms.CharField(max_length=50)
Code Explanation: The way it works is, a user(requester) will request the initiation of a pair with the PairRequestForm to the potential accepter.
The form should be pre-populated with the "favorite" of each individual user.
I'm not sure how to wire up the views.py since I need to obtain two objects.
class PairRequestView(FormView):
form_class = PairRequestForm
template_name = 'profile/pair_request.html'
success_url = "/"
def is_valid(self, form):
return
note: The pair form must be pre-populated with current information from the Profile. However, the form will not update any old information(will not save() any Profiles)--it will simply create a new Pair instance.
Assuming that the accepters page is visited at something like /profiles/2, you can capture the id of the accepter from the url, as you normally would.
Couple of things - you don't need to save the favourites into the Pair model, since for any use of the Pair model, you can just access them by doing
`p = Pair.objects.get(id=1) #or whatever query
p.requester.favourites
> Some of my favourites
p.accepter.favourites
> Some of your favourites.
If you, the proposer, are id=1, and visiting mine, the accepter's page (id=2), then you can populate the form - (I've done this in a very long manner for clarity)
accepterobj = Profile.objects.get(id=id_from_url_for_page)
proposerobj = Profile.objects.get(id=request.user.id)
form = PairRequestForm(accepter=accepterobj,
proposer=proposerobj,
accepter_favourites=accepterobj.favourites,
proposerobj_favourites=proposerobj.favourites)
in your CBV, you can do the queries above by overriding the get_initial method on your PairRequestView.
def get_initial(self):
"""
Returns the initial data to use for forms on this view.
"""
initial = super(PairRequestView, self).get_initial()
initial['proposer'] = self.request.user
...etc
return initial

CreateOrUpdateView for Django Models

In my models I have a ForeignKey relationship like this:
class Question(models.Model):
question = models.TextField(null=False)
class Answer(models.Model):
question = models.ForeignKey(Question, related_name='answer')
user = models.ForeignKey(User)
class Meta:
unique_together = (("question", "user"),)
the corresponding URL to submit an answer contains the id of the question, like this:
url(r'^a/(?P<pk>\d+)/$', AnswerQuestion.as_view(), name='answer-question'),
With user coming from self.request.user, I am trying to get something like a CreateOrUpdateView, to allow some convenient navigation for the user and URL scheme.
Until now I tried this with:
class AnswerQuestion(LoginRequiredMixin, CreateView):
and add initial value, but that isn't clean because of pk. With an UpdateView I run into problems because I have to set default values for the form.
Has anybody done something like this? I'd rather avoid having a Create and Update view for the same Answer.
The UpdateView and CreateView are really not that different, the only difference is that UpdateView sets self.object to self.get_object() and CreateView sets it to None.
The easiest way would be to subclass UpdateView and override get_object():
AnswerQuestionView(LoginRequiredMixin, UpdateView):
def get_object(queryset=None):
if queryset is None:
queryset = self.get_queryset()
# easy way to get the right question from the url parameters:
question = super(AnswerQuestionView, self).get_object(Question.objects.all())
try:
obj = queryset.get(user=self.request.user, question=question)
except ObjectDoesNotExist:
obj = None
return obj
Returns the right answer if it exists, None if it does not. Of course add any attributes you need to the class, like model, form_class etc.