Unable to get or create object within a form - python

I am trying to get or create an object when another one is created with a form :
def index(request, log_id, token):
log = get_object_or_404(LogBook, pk=log_id)
logmessages = LogMessage.objects.filter(logbook=log_id)
form = CreateLogMessage(request.POST)
if request.method == "POST":
if form.is_valid():
instance = form.save(commit=False)
instance.reported_by = request.user
instance.logbook = log
instance.save()
logdone = LogDone.objects.get_or_create(logmessage=logmessages, done_status=False)
I am trying to figure out a way to get the id of the logmessage created to pass it to my logdone instance.
I don't find a way to do it so far, any help will be appreciate it.

The object that is created is the instance, you thus can implement this as:
from django.shortcuts import redirect
def index(request, log_id, token):
log = get_object_or_404(LogBook, pk=log_id)
if request.method == 'POST':
form = CreateLogMessage(request.POST)
if form.is_valid():
form.instance.reported_by = request.user
form.instance.logbook = log
instance = form.save()
logdone = LogDone.objects.get_or_create(
logmessage=instance,
done_status=False
)
return redirect('name-of-some-view')
else:
form = CreateLogMessage(request.POST)
…
Since your form creates a new object every time, this however always create an object.
Note: In case of a successful POST request, you should make a redirect
[Django-doc]
to implement the Post/Redirect/Get pattern [wiki].
This avoids that you make the same POST request when the user refreshes the
browser.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].

Related

Python Django - Access Response Headers In View

I am working on a web application which works with entities that all have their unique IDs.
I have a submit form for users to create these entities and this form is in several steps (i.e. view 1 redirects to view 2, etc... until the end of the submission process).
The first view will create the ID of the entity after form submission and I then need to use the ID of the instance created in the other views.
I do not want to pass this ID as a URL parameter to the other views as these will be POST and that means that users could easily manipulate these and create records in models for several IDs. I have managed to pass this ID to several views using the session parameters (request.session) but this is not ideal as this is permanent for the session. Current code below:
def view1(request):
if request.method == 'POST':
form = xxx_creation_form(request.POST)
if form.is_valid():
cleaned_form_data = form.cleaned_data
xxx_entry = Model.objects.create(
... object creation ...
)
request.session['xxx_id'] = xxx_entry.id
return HttpResponseRedirect(reverse('form_2'))
else:
form = xxx_creation_form()
return render(request, 'xxx_form.html', {'form': form})
def view2(request):
xxx_id = request.session['property_id']
if xxx_id == 'SET_BACK_BLANK':
return render(request, 'no_xxx_id.html')
if request.method == 'POST':
form = xxx_form2(request.POST)
if form.is_valid():
cleaned_form_data = form.cleaned_data
xxx_entry = Model.objects.create(
id = xxx_id, #use the id created in step 1
... rest of object creation ...
)
request.session['xxx_id'] = 'SET_BACK_BLANK' #to avoid the misuse during other user interactions.
return HttpResponseRedirect(reverse('thanks'))
else:
form = xxx_form2()
return render(request, 'xxx_form2.html', {'form': form})
Ideally, I would like to pass this ID parameter in the headers of the response as this will avoid having the ID as a session parameter. So I have amended the code to the below:
def view1(request):
if request.method == 'POST':
form = xxx_creation_form(request.POST)
if form.is_valid():
cleaned_form_data = form.cleaned_data
xxx_entry = Model.objects.create(
... object creation ...
)
response = HttpResponseRedirect(reverse('form_2'))
response['xxx_id'] = xxx_entry.id
return response
else:
form = xxx_creation_form()
return render(request, 'xxx_form.html', {'form': form})
def view2(request):
xxx_id = HttpResponseRedirect(request).__getitem__('xxx_id')
if request.method == 'POST':
form = xxx_form2(request.POST)
if form.is_valid():
cleaned_form_data = form.cleaned_data
xxx_entry = Model.objects.create(
id = xxx_id, #use the id created in step 1
... rest of object creation ...
)
return HttpResponseRedirect(reverse('thanks'))
else:
form = xxx_form2()
return render(request, 'xxx_form2.html', {'form': form})
However the above does not work and the error message seems to indicate that there is no 'xxx_id' in the response header.
It would be great if anyone could let me know how to access a response's header in a view as it seems that we cannot amend the request's headers.
Thanks.
What you're asking doesn't really make sense. The response is what is sent from Django to the browser, it is not what the browser sends to Django. That's a completely separate request; in the case of a redirect, your response is simply an instruction to the browser to make that request to a new URL.
The correct thing to do is to use the session, as you are doing. If you are worried about the value being persisted, then pop it out of the session when you use it:
xxx_id = request.session.pop('property_id')

Django redirect, but pass form error context

I have a very simple index page view, from which the user can fill in a login popup, which sends a post request to /login
def index(request):
"""Shows list of studyspaces, along with corresponding 'busyness' score"""
context = {'study_space_list': StudySpace.objects.order_by('-avg_rating')}
if request.user.is_authenticated():
context['user'] = request.user
else:
context['login_form'] = LoginForm()
context['user_form'] = UserForm()
context['student_form'] = StudentForm()
return render(request, 'spacefinder/index.html', context)
If the login is valid it simply redirects to the index page, this works fine.
The login view looks as follows:
def user_login(request):
form = LoginForm(request.POST)
if request.method == 'POST' and form.is_valid():
user = form.login(request)
if user:
login(request, user)
return redirect(reverse('spacefinder:index'))
# Load the context all over again
context = {
'study_space_list': StudySpace.objects.order_by('-avg_rating')
}
context['login_form'] = form
context['user_form'] = UserForm()
context['student_form'] = StudentForm()
return render(request, 'spacefinder/index.html', context)
However when the login is incorrect I want to be able to refresh the page and show the login form errors inside the index template (in the login popup)
I'm actually able to achieve this with the above code, but I'm unhappy with the solution for the following reasons:
I have to manually fetch the context all over again, e.g user/student forms and studyspaces, this goes against the DRY principle
When the page is refreshed the url is localhost:8000/spacefinder/login
Screenshot of behaviour here
I'm wondering if there's somehow a way to use redirect to reload the index page and somehow pass errors from my login_form, e.g. something like:
return redirect('spacefinder:index', {'login_form': form})
I've looked into using messages to pass form validation errors, but struggled to get this working since Validation Errors are thrown inside forms.py, and I'm unable to fetch the request instance from inside a ModalForm to properly create a message
You are doing it the wrong way around.
Consider these prerequisites:
entry point to your page is the index view
the index view must only be accessible by authenticated users
the login view allows both methods GET and POST and is accessible to anonymous users only
The reason to use Django is to make use of all the features that it offers, and that includes handling of the above (because that is what most pages need, not only you).
To set it up correctly you need to define your urls.py like this:
from django.contrib.auth.decorators import login_required
urlpatterns = [
....
url('^login/$', user_login, 'login'),
url('^/$', login_required(index), 'index'),
....
]
In your settings/base.py (or settings.py if you have no environment differentiation) tell Django how to redirect users:
LOGIN_URL = reverse_lazy('login')
LOGIN_REDIRECT_URL = reverse_lazy('index')
https://docs.djangoproject.com/en/1.9/ref/settings/#login-url
https://docs.djangoproject.com/en/1.9/ref/settings/#login-redirect-url
Simplify your index view:
def index(request):
"""Shows list of studyspaces, along with corresponding 'busyness' score"""
context = {'study_space_list': StudySpace.objects.order_by('-avg_rating')}
if request.user.is_authenticated():
context['user'] = request.user
else:
return HttpResponseForbidden() # prevented by Django, should never happen
return render(request, 'spacefinder/index.html', context)
Let the user_login view deliver the empty login form:
#require_http_methods(["GET", "POST"])
def user_login(request):
params = getattr(request, request.method)
form = LoginForm(params)
if request.method == 'POST' and form.is_valid():
user = form.login(request)
if user:
login(request, user)
return redirect(reverse('spacefinder:index'))
# Load the context for new form or form with errors
context = {
'study_space_list': StudySpace.objects.order_by('-avg_rating')
}
context['login_form'] = form
context['user_form'] = UserForm()
context['student_form'] = StudentForm()
return render(request, 'spacefinder/index.html', context)
You have not presented any code that handles the UserForm or the StudendForm. You would need to add that to the user_login view, as well - if this is something that all users should fill in every time they login. Otherwise use a different view.
It's worth looking at modules like allauth. They might spare you some work when it comes to allowing users to login with their e-mail addresses, ascertain that e-mail addresses are unique in the system etc.

django-form show form data after saving it

I want to show same data to user as posted by him using form after saving it in database.
I am not getting the logic for it.
I am trying to do something like this:
def CreateDeal(request):
if request.method == "POST":
form = DealForm(request.POST)
if form.is_valid():
form.save(commit = True)
data = form.data
return render(request, '/path_to/deal_detail.html',data=data)
Is it ok ?
Is there any better way to do it?
If you do it this way, a redirect of the "detail" page will resubmit the form. This is generally not desired behaviour.
A better way would be to create a detail view for you saved object (if you haven't already) and redirect the user to the detail view of that particular object:
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
def CreateDeal(request):
if request.method == "POST":
form = DealForm(request.POST)
if form.is_valid():
obj = form.save(commit=True)
return HttpResponseRedirect(reverse('deal-detail-view', args=(obj.id,)))
# or return HttpResponseRedirect(obj.get_absolute_url())
# if `get_absolute_url` is defined

Django - best practice for getting ID from querystring into edit view

I'm wondering what the acceptable best practice is for pulling an id from a url for use in the edit view. Most example code I see uses slugs, which I don't need to deal with because SEO is not a concern.
Say I have something like:
def article_edit(request):
if request.method == 'POST': # If the form has been submitted...
#a = get_object_or_404(Article, **?XXX?**)
#a = Article.objects.get(pk=**?XXX?**)
form = ArticleForm(request.POST, instance=a) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
form.save()
return redirect('/articles/') # Redirect after POST
else:
form = ArticleForm() # An unbound form
return render(request, 'article_form.html', {'form': form})
Where I have commented out two possible options for populating a with an Article object based on the ID submitted in the POST. The ?XXX? indicates that I'm not sure how to reference the passed in id.
Any input on those two options, as well as alternative options are appreciated.
Passed in id should go in the url itself, like:
url(r'^articles/(?P<id>\d+)/edit/$', 'views.article_edit', name = 'article_edit'),
Then, in the view you can reference it from the view argument id:
def article_edit(request, id):
if request.method == 'POST': # If the form has been submitted...
article = get_object_or_404(Article, pk=id)
Also, take a look at Writing a simple form chapter of django "polls" tutorial - the same approach is used.
Hope that helps.
try this:
urls.py :
url(r'^articles/(?P<article_id>\d+)/edit/$', 'views.article_edit', name = 'article'),
views.py:
def article_edit(request, id):
if request.method == 'POST':
article = get_object_or_404(Article,id=article_id)

Django: construct form without validating fields?

I have a form MyForm which I update using ajax as the user fills it out. I have a view method which updates the form by constructing a MyForm from request.POST and feeding it back.
def update_form(request):
if request.method == 'POST':
dict = {}
dict['form'] = MyForm(request.POST).as_p()
return HttpResponse(json.dumps(dict), mimetype='application/javascript')
return HttpResponseBadRequest()
However, this invokes the cleaning/validation routines, and I don't want to show error messages to the user until they've actually actively clicked "submit".
So the question is: how can I construct a django.forms.Form from existing data without invoking validation?
Validation never invokes until you call form.is_valid().
But as i am guessing, you want your form filled with data user types in, until user clicks submit.
def update_form(request):
if request.method == 'POST':
if not request.POST.get('submit'):
dict = {}
dict['form'] = MyForm(initial = dict(request.POST.items())).as_p()
return HttpResponse(json.dumps(dict), mimetype='application/javascript')
else:
form = MyForm(request.POST)
if form.is_valid():
# Your Final Stuff
pass
return HttpResponseBadRequest()
Happy Coding.

Categories

Resources