First off, sorry if I am not clear on what I'm trying to do or if I don't give enough info, I'm still relatively new to Django/Python.
I currently have a view that renders something like a blog post:
class SingleCharacter(LoginRequiredMixin,generic.DetailView):
model = models.Character
def get_context_data(self, **kwargs):
context = super(SingleCharacter, self).get_context_data(**kwargs)
return context
And, in the template for this view, I have a template tag that checks if the user is authenticated AND the owner of the post:
{% if user.is_authenticated and user == character.user %}
However, I'm currently in the process of incorporating xhtml2pdf into my project and this method of securing the post to only the user who created it is causing some issues.
I'm wondering if it's possible to move the user.is_authenticated and user == character.user into the view instead of a template tag, and if so can i do it with a simple if statment, something like this?
class SingleCharacter(LoginRequiredMixin,generic.DetailView):
model = models.Character
if user.is_authenticated and user == character.user:
def get_context_data(self, **kwargs):
context = super(SingleCharacter, self).get_context_data(**kwargs)
return context
else:
<I'll include some redirect to a 404 page>
I'm trying to see if there's another way to do it, but I thought I'd throw this out here to the more experienced people in hopes of figuring it out.
Thanks for any help!
You can override the get method of the DetailView and handle the logic there:
class SingleCharacter(LoginRequiredMixin,generic.DetailView):
model = models.Character
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if self.request.user.is_authenticated and self.request.user == self.object.user:
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
else:
# redirect
Related
I'm working with an e-commerce webapp and in the product detailed-view I want to give the option to set the quantity before the checkout. I thought I could do this with a simple form:
class QuantityForm(forms.Form):
quantity = forms.IntegerField(widget=forms.NumberInput(attrs={'class':'required'}))
And then implementing it to the view:
class BagDetailView(DetailView):
context_object_name = 'ctx'
model = Bag
template_name = 'product_page.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = QuantityForm(initial={'quantity':1})
context['Jackson'] = Bag.objects.get(title = 'Jackson')
context['Anthony'] = Bag.objects.get(title = 'Anthony')
return context
But the problem is the POST request. I cannot call the render() function because I have another view to handle the checkout, so I need to call redirect(). This is the code:
def post(self, *args, **kwargs):
form = QuantityForm()
b = Bag.objects.get(slug=kwargs['slug'])
if form.is_valid:
return redirect(b)# calls get_absolute_url which redirects to checkout/<slug>
And I cannot access the data that I'm posting in the other view. I think it's bad practise to post data like this to another view, but I couldn't come up with anything but that. Is there any other way to pass that data into the other view?
Note: I'm not using a cart system because I'm only dealing with two products.
I am not sure if I have understood correctly, maybe you can do something like this.
In the template for increasing quantity, you can put this:
<i class="">INCREASE QUANTITY</i>
When the user clicks this, a function will be called:
#login_required
def add_to_checkout(request, slug):
#get the checkout for this customer which is not fulfilled
#increase the quantity
#redirect to checkout page
Another idea can be using session. You can increase the amount in the detail page and save it to the sessions, then use in the checkout page.
I hope I have understood correctly your question.
I'm new to django.
I'd like my users to be able to place orders. For this kind of order, the user uploads a csv. The app parses, the CSV, serializes the data, and then needs to show the user a new view with the data and a "confirm order" button. I'm not sure how to do this in django.
class UploadSampleSheetView(LoginRequiredMixin, FormView):
form_class = UploadSampleSheetForm
template_name = 'pages/upload.html'
def form_valid(self, form):
uploaded_sample_sheet = self.request.FILES['uploaded_sample_sheet']
sample = _parse_sample_sheet_to_sample_model(uploaded_sample_sheet)
sample.save()
return super().form_valid(form)
def get_success_url(self):
return reverse("orders:create") # how do I return another view populated with data here?
class CreateOrderView(LoginRequiredMixin, CreateView):
model = Order
form_class = NewOrderForm
template_name = 'pages/complete_order.html'
What I'm looking for is some way that, on success, the UploadSampleSheetView can return a CreateOrderView with sample data.
In general, I'd love to be pointed to a reference about how to build user flows like this one. How does one view defer to another? I'm seeing a lot of return HttpResponseRedirect('url') which seems a little messy. How do I pass data around views?
FormViews are supposed to return HttpResponseRedirects. They take in the data, process it, and re-route you to a place that makes sense. I think your question is, how can I get routed to the appropriate place.
The documentation isn't very clear on this, so it's a very valid question. But, you can override get_success_url to take in an argument. So, you would add this:
def get_success_url(self, sample_id):
return reverse('create_order', kwargs={"pk" : sample_id})
and also change form_valid to
def form_valid(self, form):
uploaded_sample_sheet = self.request.FILES['uploaded_sample_sheet']
sample = _parse_sample_sheet_to_sample_model(uploaded_sample_sheet)
sample.save()
return HttpResponseRedirect(self.get_success_url(sample.id))
Don't worry, you're not missing out on anything by skipping super().form_valid() -- the docs (scroll just a little down) say that all form_valid does by default is redirect to success_url.
You can use Form Wizard
Django comes with an optional “form wizard” application that splits forms across multiple Web pages. It maintains state in one of the backends so that the full server-side processing can be delayed until the submission of the final form.
You might want to use this if you have a lengthy form that would be too unwieldy for display on a single page. The first page might ask the user for core information, the second page might ask for less important information, etc.
Harder to use, but very useful and flexible.
# views.py
from formtools.wizard.views import SessionWizardView
class MyView(SessionWizardView):
template_name = 'pages/wizard_steps.html'
template_name_done = 'pages/wizard_done.html'
form_list = (
('upload', UploadSampleSheetForm),
('confirm', NewOrderForm)
)
def done(self, form_list, **kwargs):
# you can get all forms data here and process it as you want
return render(self.request, self.template_name_done, {
'form_data': [form.cleaned_data for form in form_list],
})
def process_step(self, form):
if isinstance(form, UploadSampleSheetForm):
# Example. You can put here some additional manipulations
pass
return super().process_step(form)
And template
<!-- wizard_done.html -->
{% if wizard.steps.current == 'upload' %}
{% include 'pages/wizard_upload.html' %}
{% elif wizard.steps.current == 'complete' %}
{% include 'pages/wizard_complete.html' %}
{% endif %}
I think you are looking for Django Session. This is an extensive document if you want to dive deep.
It includes the examples that you are looking for as well.
I have the following view in my django app.
def edit(request, collection_id):
collection = get_object_or_404(Collection, pk=collection_id)
form = CollectionForm(instance=collection)
if request.method == 'POST':
if 'comicrequest' in request.POST:
c = SubmissionLog(name=request.POST['newtitle'], sub_date=datetime.now())
c.save()
else:
form = CollectionForm(request.POST, instance=collection)
if form.is_valid():
update_collection = form.save()
return redirect('viewer:viewer', collection_id=update_collection.id)
return render(request, 'viewer/edit.html', {'form': form})
It displays a form that allows you to edit a collection of images. The footer of my html contains a form that allows you to request a new image source from the admin. It submits to a different data model than the CollectionForm. Since this is in the footer of every view, I want to extract lines 5-7 of the code and turn it into a decorator. Is this possible and if so how might I go about doing that?
I would make a new view to handle the post of the form. And then stick a blank form instance in a context processor or something, so you can print it out on every page.
If you do want to make a decorator, i would suggest using class based views. That way, you could easily make a base view class that handles the form, and every other view could extend that.
EDIT:
Here's the docs on class based views: https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/
Note, I would still recommend having a separate view for the form POST, but here's what your solution might look like with class based views:
class SubmissionLogFormMixin(object):
def get_context_data(self, **kwargs):
context = super(SubmissionLogFormMixin, self).get_context_data(**kwargs)
# since there could be another form on the page, you need a unique prefix
context['footer_form'] = SubmissionLogForm(self.request.POST or None, prefix='footer_')
return context
def post(self, request, *args, **kwargs):
footer_form = SubmissionLogForm(request.POST, prefix='footer_')
if footer_form.is_valid():
c = footer_form.save(commit=False)
c.sub_date=datetime.now()
c.save()
return super(SubmissionLogFormMixin, self).post(request, *args, **kwargs)
class EditView(SubmissionLogFormMixin, UpdateView):
form_class = CollectionForm
model = Collection
# you can use SubmissionLogFormMixin on any other view as well.
Note, that was very rough. Not sure if it will work perfectly. But that should give you an idea.
I have created a Class view in views.py of the django application.
class HelloTemplate(TemplateView):
template_name = "index.html"
def get_context_data(self, **kwargs):
context = super(HelloTemplate, self).get_context_data(**kwargs)
return context
Now I have a form defined in the html page:
<form method="get">
<input type="text" name="q">
<input type="text" name="q1">
<input type="submit" value="Search">
</form>
As you can see, I am submitting the form on the same page.
Now I want to get the form submitted values in my HelloTemplate class. I don't want to create another class or methods outside the existing class.
Also, I would like to send an error message to the html form if data is not validated in the django.
I don't know how to do this, please help me out.
You need to define get (because your form defined with get method <form method="get">) method in view class:
class HelloTemplate(TemplateView):
template_name = "index.html"
def get_context_data(self, **kwargs):
context = super(HelloTemplate, self).get_context_data(**kwargs)
return context
def get(self, request, *args, **kwargs):
q = request.GET.get('q')
error = ''
if not q:
error = "error message"
return render(request, self.template_name, {'error': error})
More information in django docs here Introduction to Class-based views
There's only one value, and it's in request.GET['q'].
Quick response, I can show you what I did a while ago for a review form (for people to create a new review, one of my models):
def review_form_view(request):
c = {}
c.update(csrf(request))
a = Review()
if request.method == 'POST':
review_form = Review_Form(request.POST, instance=a)
if review_form.is_valid():
a = review_form.save()
return HttpResponseRedirect('../dest_form_complete')
pass
else:
review_form = Review_Form(instance=a)
return render_to_response('../review_form.html', {
'review_form': review_form,
}, context_instance=RequestContext(request))
If you have a user model, comment model, etc. you can probably use something similar to this. Very (very) roughly put, the request is the input that the user fills out in the form, 'POST' is the method called that lets the server know you are adding entries to your database, and is_valid() validates the data according to your models.py parameters (can name be NULL? Is age an integer? etc).
Take a look at https://docs.djangoproject.com/en/dev/topics/forms/ as well for more examples and explanation.
I'm new to Django and looking for best practices on the code I just wrote (below). The code currently exists in my view.py and simply creates a new event. Being familiar with other languages it just 'smells bad' if you know what I mean. Could someone point out how they would do this simple task.
The only thing looking at my code again (and from reading the docs a little more), would be to move the request.user into the models.py save function.
Anything else which is a big newbie mistake below?
#login_required
def event_new(request):
# If we had a POST then get the request post values.
if request.method == 'POST':
form = EventForm(request.POST)
# Check we have valid data
if form.is_valid():
# If form has passed all validation checks then continue to save it.
city = City.objects.get(name=form.cleaned_data['autocompleteCity'])
category = form.cleaned_data['category']
event = Event(
name=form.cleaned_data['name'],
details=form.cleaned_data['details'],
date=form.cleaned_data['date'],
start=form.cleaned_data['start'],
end=form.cleaned_data['end'],
category=category,
city=city,
user=request.user,
)
event.save()
messages.add_message(request, messages.SUCCESS, 'Event has been created.')
return HttpResponseRedirect('/events/invite/')
else:
messages.add_message(request, messages.ERROR, 'Error')
context = {'form': form}
return render_to_response('events/event_edit.html', context, context_instance=RequestContext(request))
else:
form = EventForm
context = {'form': form}
return render_to_response('events/event_edit.html', context, context_instance=RequestContext(request))
You should read about create forms from models. The ModelForm class will save you from copying the fields from the form to the model.
Apart from that this view looks pretty normal to me.
You can even get rid of some of the boilerplate code (if request.method == "POST", if form.is_valid(), etc.) with the generic FormView or CreateView. Since you seam to have some special form handling it might not be of any use for you, but it might be worth a look.
This code is not 100% complete (your special logic for cities is missing) but apart from that should be pretty complete and give you an idea how generic views could be used.
forms.py
from django.forms import ModelForm
class EventForm(ModelForm):
def __init__(self, user, **kwargs):
self.user = user
super(EventForm, self).__init__(**kwargs)
class Meta:
model = Event
def save(self, commit=True):
event = super(EventForm, self).save(commit=False)
event.user = self.user
if commit:
event.save()
views.py
from django.views.generic.edit import CreateView
class EventCreate(CreateView):
model = Event
form_class = EventForm
template = "events/event_edit.html"
success_url = "/events/invite/" # XXX use reverse()
def get_form(form_class):
return form_class(self.request.user, **self.get_form_kwargs())
def form_valid(form):
form.user = self.request.user
messages.success(request, 'Event has been created.')
super(EventCreate, self).form_valid(form)
def form_invalid(form):
messages.error(request, 'Error')
super(EventCreate, self).form_invalid(form)
urls.py
url(r'event/add/$', EventCreate.as_view(), name='event_create'),
I think this looks great. You have followed the conventions from the docs nicely.
Moving request.user to a model would certainly be an anti pattern - a views function is to serve in the request/response loop, so access this property here makes sense. Models know nothing of requests/responses, so it's correct keep these decoupled from any view behavior.
Only thing that I noticed was creating a category variable out only to be used in construction of the Event, very minor.