I have a problem converting from a function-based view to a class-based view, function
VIEWS.PY
# login_required
def favourite_add(request, id):
post = get_object_or_404(Perfumes, id=id)
if post.favourites.filter(id=request.user.id).exists():
post.favourites.remove(request.user)
else:
post.favourites.add(request.user)
return HttpResponseRedirect(request.META['HTTP_REFERER'])
URLS.PY
urlpatterns = [
path('fav/<int:id>/', views.favourite_add, name='favourite_add'),
]
TEMPLATE.HTML
<div>
Add
</div>
In general, the goal is to get the id of a certain perfume on the page, and using the get_object_or_404 function, I'm pulling its object from the Perfumes database - the post variable. Next, I want to retrieve the id of the logged-in user and check if the id of the above user is in the favourites section of the post variable. If not then add, otherwise remove the user id to the favourites section of the post variable.
You should not do this through a GET request, as the safe methods section of the HTTP specifications [w3.org] says:
In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe".
GET and HEAD are thus supposed to have no side effects. You can for example work with a POST request:
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
class FavouriteView(LoginRequiredMixin, View):
def post(self, request, id):
perfume = get_object_or_404(Perfumes, id=id)
if request.user in perfume.favourites.all():
perfume.favourites.remove(request.user)
else:
perfume.favourites.add(request.user)
return HttpResponseRedirect(request.META['HTTP_REFERER'])
You then make a mini-form to make a POST request:
<form method="post" action="{% url 'favourite_add' perfume.id %}">
{% csrf_token %}
<button class="btn btn-outline-primary">Add</button>
</form>
Note: normally a Django model is given a singular name, so Perfume instead of Perfumes.
A function based one probably works fine for what you require.
Anyway, here is a view that should perform the same job as yours:
urls.py:
urlpatterns = [
path('fav/<int:id>/', views.FavouriteView.as_view(), name='favourite_add'),
]
views.py:
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
class FavouriteView(LoginRequiredMixin, View):
def get(self, *args, **kwargs):
post = get_object_or_404(Perfumes, id=self.kwargs.get('id'))
if post.favourites.filter(id=self.request.user.id).exists():
post.favourites.remove(self.request.user)
else:
post.favourites.add(self.request.user)
return HttpResponseRedirect(self.request.META['HTTP_REFERER'])
I recommend looking at something like https://ccbv.co.uk/ to help you understand class based views
Related
I created a small Django application to manage data that fits a simple a model. For now I only need two views: one to list all records and another to edit a record with a generic form. Everything functions as expected, except the redirection from the edit view upon a successful update. In urls.py are the following contents:
from django.urls import path
from . import views
app_name = 'reqs'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.ReqUpdateView.as_view(), name='update'),
]
In forms.py:
from django.forms import ModelForm
from .models import Requirement
class RequirementForm(ModelForm):
class Meta:
model = Requirement
fields = ['name', 'priority', 'source' , 'rationale']
And the templeate requirement_form.html:
<h1>{{ requirement.id }} - {{ requirement.name }}</h1>
<form method="post" novalidate>
{% csrf_token %}
<table>
{{ form.as_table }}
<tr><td></td><td><button type="submit">Save</button></td></tr>
</table>
</form>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<br><br>
Back to list
Finally views.py, on a first attempt to redirect the update to the list:
from django.views.generic import ListView, UpdateView
from django.urls import reverse_lazy
from .models import Requirement
from .forms import RequirementForm
class IndexView(ListView):
template_name = 'reqs/index.html'
context_object_name = 'requirements_list'
def get_queryset(self):
return Requirement.objects.order_by('subject')
class ReqUpdateView(UpdateView):
model = Requirement
form_class = RequirementForm
success_url = reverse_lazy('/')
With this formulation the Save button produces this error:
Reverse for '/' not found. '/' is not a valid view function or pattern name.
I also tried an empty string as argument to reverse_lazy, as well as the path name index, but a similar error message is produced.
On a second attempt I tried to redirect to the same page, redefining the get_success_url method to do nothing:
class ReqUpdateView(UpdateView):
model = Requirement
form_class = RequirementForm
context_object_name = 'requirement_update'
def get_success_url(self):
pass #return the appropriate success url
This returns a 404 error trying to redirect the browser to /reqs/1/None.
A third attempt to redirect to the form with the same record:
class ReqUpdateView(UpdateView):
model = Requirement
form_class = RequirementForm
context_object_name = 'requirement_update'
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse("update", kwargs={"pk": pk})
Which complains about not finding the view:
Reverse for 'update' not found. 'update' is not a valid view function or pattern name.
How can I redirect success to a valid URL? It can either be the items list or the item update view, as long as it works.
There are few misconception that you did
reverse parameter should be as documented
URL pattern name or the callable view object
You have set namespace but you are not reversing with namespace as documented
So in your case
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse("reqs:update", kwargs={"pk": pk})
reverse / reverse_lazy are used to get the url using view name or pattern name. If you want to use a url directly just write:
success_url = '/'
For the case of return reverse("update", kwargs={"pk": pk}) not working since you set app_name = 'reqs' you should be using return reverse("reqs:update", kwargs={"pk": pk}) instead.
I am trying to show a newsletter form, and it is not shown in the page
This is my models.py
from django.db import models
# Create your models here.
class newsletter_user(models.Model):
email = models.EmailField()
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.email
This is my forms.py
from django import forms
from .models import newsletter_user
class newsletterForm(forms.ModelForm):
class Meta:
model = newsletter_user
fields = ['email']
def clean_email(self):
email = self.cleaned_data.get('email')
return email
This is my admin.py
from django.contrib import admin
from .models import newsletter_user
# Register your models here.
class newsletterAdmin(admin.ModelAdmin):
list_display = ('email','date_added',)
admin.site.register(newsletter_user,newsletterAdmin)
This is the views.py
from django.shortcuts import render
from .models import newsletter_user
from .forms import newsletterForm
# Create your views here.
def newsletter_subscribe(request):
form = newsletterForm(request.POST or none)
if form.is_valid():
instance = form.save(commit=false)
if newsletter_user.objects.filter(email=instance.email).exists():
print("already exists")
else:
instance.save()
context = {'form':form,}
template = "/blog/templates/footer.html"
return render(request, template, context)
This is the html
<form method="post" action=''>
<div class = "input-group">
{{form}} {% csrf_token %}
<span class = "input-group-btn">
<button class="btn btn-default" type="submit">Subscribe</button>
</span>
</div>
</form>
This is my urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', views.BlogIndex.as_view(), name='home'),
url(r'^(?P<slug>[-\w]+)/$', views.BlogDetail.as_view(), name='entry_detail'),
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
url(r'^footer/$', subscribe_views.newsletter_subscribe, name='subscribe'),
]
My Project directory
The button is shown
But the form is not shown..
This is my source in web browser RIGHT-CLICK->VIEW SOURCE
The url router will send the request to the first matching view. That is the only one that is called, and that view has to provide the context data that the template consumes. (You can also write your own context processor to insert context that you need everywhere.)
Since another pattern also matches /footer/, your request is clearly handled by some other view.
url(r'^(?P<slug>[-\w]+)/$', views.BlogDetail.as_view(), name='entry_detail'),
If the other view doesn't provide form into the context, there's nothing for Django to render.
Your view function newsletter_detail() is not called from other views, so that context is not used. (Using the undefined none there would have caused a run time error, which shows that the code was never evaluated.)
Catch-all routes such as entry_detail should either be used as the last url route, or be made more specific. Something like r'^/blog/(?P<slug>[-\w]+)/$', for instance, which will not match /footer/.
For a simple "subscribe" form in the footer, I recommend writing it as just html, and set up a route /subscribe/ to handle POST requests. There's not anything to gain by using Django's form framework for such a simple case (Just one field).
The django docs has an example of how you can implement something like this.
You footer.html template fragment should not require any context that is not automatically inserted by a context processor. Django's CsrfViewMiddleware provides the {% csrf_token %}, so that's an example of something you can use in template fragments such as a footer.
If you need some complicated form in your footer, you can write custom middleware to insert a Django Form instance in every context, (but you should probably give it a less generic name than form).
You may need to make some changes in your view somewhat like this,
def newsletter_subscribe(request):
if request.method == 'POST':
form = newsletterForm(request.POST)
if form.is_valid():
instance = form.save(commit=false)
if newsletter_user.objects.filter(email=instance.email).exists():
print("already exists")
else:
instance.save()
else:
form = newsletterForm()
context = {'form':form,}
template = "/blog/templates/footer.html"
return render(request, template, context)
You only need to initialise the form with request.POST , if request method is actually "POST". Else, just initialise a blank form.
I am new to Django and have been making a sample project. I have been trying to use Generic Detailview. It seems that url redirection works fine but DetailView can't get primarykey from the url.
Main url.py::
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^',include('personal.urls')),
]
Here is my app's urls.py code:
urlpatterns = [
url(r'(?P<pk>\d+)/$',views.detailView.as_view(),name="detail"),]
View file for the DetailView:
from django.shortcuts import render
from django.views import generic
from .models import Story
class detailView(generic.DetailView):
model = Story
template_name = 'personal/storydetail.html'
def get_context_data(self, **kwargs):
pk = kwargs.get('pk') # this is the primary key from your URL
print("PK:",pk)
Template Code:
{% block content %}
{{ Story.writer }}
<h6> on {{ Story.story_title }}</h6>
<div class = "container">
{{ Story.collection }}
</div>
{% endblock %}
Story Class code:
class Story(models.Model):
story_title = models.CharField(max_length=200) #Story title
writer = models.CharField(max_length=200) #WriterName
collection=models.CharField(max_length=200) #Collection/Book name
When I check primary key value on view it shows it 'NONE'. I can't find issue with the code. My pased url looks like : http://127.0.0.1:8000/personal/2/ where personal is the name of app and 2 should be taken as id.
The problem is that you are using kwargs instead of self.kwargs inside the get_context_data method. It should be something like:
def get_context_data(self, **kwargs):
# You need to call super() here, so that the context from the DetailView is included
kwargs = super(detailView, self).get_context_data(**kwargs)
pk = self.kwargs['pk'] # No need for get() here -- if you get a KeyError then you have a problem in your URL config that should be fixe # this is the primary key from your URL
# edit kwargs as necessary
...
return kwargs
In the get_context_data method, kwargs are those passed to the method to make up the context. They are different from self.kwargs, which are from the url pattern.
What's the best way to add a "cancel" button to a generic class-based view in Django?
In the example below, I would like the cancel button to take you to success_url without deleting the object. I have tried adding a button <input type="submit" name="cancel" value="Cancel" /> to the template. I can detect if this button was pressed by overriding the post method of the AuthorDelete class, but I can't work out how to redirect from there.
Example myapp/views.py:
from django.views.generic.edit import DeleteView
from django.core.urlresolvers import reverse_lazy
from myapp.models import Author
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
def post(self, request, *args, **kwargs):
if request.POST["cancel"]:
return ### return what? Can I redirect from here?
else:
return super(AuthorDelete, self).post(request, *args, **kwargs)
Example myapp/author_confirm_delete.html:
<form action="" method="post">{% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p>
<input type="submit" value="Confirm" />
<input type="submit" name="cancel" value="Cancel" />
</form>
(Examples adapted from the docs)
Your approach of overriding the post method and checking to see if the cancel button was pressed is ok. You can redirect by returning an HttpResponseRedirect instance.
from django.http import HttpResponseRedirect
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
def post(self, request, *args, **kwargs):
if "cancel" in request.POST:
url = self.get_success_url()
return HttpResponseRedirect(url)
else:
return super(AuthorDelete, self).post(request, *args, **kwargs)
I've used get_success_url() to be generic, its default implementation is to return self.success_url.
Why don't you simply put a "Cancel" link to the success_url instead of a button? You can always style it with CSS to make it look like a button.
This has the advantage of not using the POST form for simple redirection, which can confuse search engines and breaks the Web model. Also, you don't need to modify the Python code.
If using CBV's you can access the view directly from the template
Cancel
Note: you should access it through the getter in case it has been subclassed.
This is noted in the ContextMixin docs
The template context of all class-based generic views include a view
variable that points to the View instance.
Having an element of type button, will not send a POST request. Therefore, you can use this to do a http redirection like this:
<button type="button" onclick="location.href='{{ BASE_URL }}replace-with-url-to-redirect-to/'">Cancel</button>
Do you even need the get_success_url, why not just use:
Cancel
and go to any other url you want?
can i add the ajax code with django?
i have created a simple registraion form that have 5 fields . i wish to disply the each fields in different pages but in a single window . it means by using next button 5 pages want to disply in a single window. same time all content of each page i want add to my database. is this possible in django with ajax..
my codes are as follows :
#view
from django.shortcuts import render_to_response
from registration.models import UserDetails
from forms import UserForm
from django import forms
from django.template import RequestContext
from django.http import HttpResponseRedirect
def user_details(request):
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():
form.save()
else:
form = UserForm()
return render_to_response("career.html", {"form": form},context_instance=RequestContext(request))
#form
from django import forms
from registration.models import UserDetails
class UserForm(forms.ModelForm):
pass
class Meta:
model = UserDetails
#model
from django.db import models
class UserDetails(models.Model):
fname=models.CharField(max_length=20)
lname=models.CharField(max_length=20)
email = models.EmailField()
address = models.CharField(max_length=50)
country = models.CharField(max_length=20)
def __unicode__(self):
return self.fname
return self.lname
return self.email
return self.address
return self.country
#url
from django.conf.urls.defaults import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
url(r'^registration/$', 'registration.views.user_details', name='user_details'),
url(r'^admin/', include(admin.site.urls)),
)
# template
<form enctype="multipart/form-data" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" ....>
</form>
Whay have you tried for ajax call?
Server just serve some result to client then it is up to your client code:
either it is post back that you refresh page
or iframe, you refresh a frame in parent doc
or html tag like tag that you inject by $(targetElement).html($someResultFromServer)
In most case server does not even care what and how it looks like client(rich or thin),
Thats you javascript, query and css codes which to works on behalf of client. Vice versa, in most case client does not even care what is and how it looks like it is server: Loosely coupled
For ajax calls you can follow this link: http://api.jquery.com/jQuery.ajax/
As Martin Thurau stated your question is very hard to understand. Regardless I think that what you are asking for is a stepped form.
Best you take a look at Django's Form Wizard here