Deleting and creating in CreateView django - python

Hello I am making django to-do list and I have a question. Am i able to delete and create elements in one view? Personally I would not use CreateView for that and make it like in 'def post' and ignore 'get_success_url' but is it good and djangonic practice?
views.py
class Home(CreateView):
model = Item
template_name = 'home/todo.html'
form_class = itemCreationForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['items'] = Item.objects.all()
return context
def get_success_url(self):
return reverse('home-page')
def post(self, request):
form = itemCreationForm()
if 'delete' in self.request.POST:
print('deleted')
return redirect('home-page')
if 'add' in self.request.POST and form.is_valid():
form.save()
return HttpResponse('home-page')
HTML
<div class="card__items-list">
{% for item in items %}
<div class="card__item-row">
<p class="card__item">{{ item.name }}</p>
<form action="{% url 'home-page' %}" class="card__delete" method="POST"> {% csrf_token %}
<button class="card__delete__button" name="delete" type="submit">&#10008</button>
</form>
</div>
{% endfor %}
</div>
<form name="add" method="POST"> {% csrf_token %}
{{ form }}
<button name="add" class="submit-button" type="submit">Submit</button>
</form>
</div>

It is possible to handle both the deletion and creation of objects in a single Django view function. However, it is arguably a better practice to handle these operations in separate HTTP action verbs (POST vs DELETE).
For example, your Home view could handle both of these operations, but separate them into different if blocks based on the HTTP action verb of the request. Then, from your web page, the create operation would send an HTTP POST request, and the delete operation would send an HTTP DELETE request.
This could be done in a single Django view function using the following sample code:
def my_sample_view(request):
if request.method == "POST":
# Handle create logic here.
elif request.method == "DELETE":
# Handle delete logic here.

Yes, you can do this.
Personally, I would use FormView not CreateView, and do the POST processing in the form_valid method. It's contrary to one's expectations for a CreateView to delete something, whereas processing a form can basically do anything.

Related

How to loop through a form and add same form in django if we click add more button and store that in django

What I really want to do is , if a user click on "ADD more" button then a same form repeat itself and the values should store in database, if he/she doesn't click of that button then only the values from first form should be stored.
I am not able to get this, I just created a form , and a table in database for those details but can't loop though the form neither in data.
please help.
This is the form and the button:
This is the model.py code:
from django.db import models
class experience(models.Model):
company_name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
startdate = models.Datefield(default = 01-01-2020)
lastdate = models.DateField(default = 01-01-2020)
profile = models.CharField(max_length=100)
description = models.TextField(max_length = 250)
This is the views.py code:
from django.shortcuts import render, redirect
import requests
from django.contrib.auth.models import User, auth
# Create your views here.
def profile(request):
return render(request, 'profile.html')
Unfortunately, there's no built-in way (as far as I know) in Django to do that without Javascript, but here's an approach:
HTML:
<div class="container" id="experiencesContainer">
<form method='POST' name='experienceForm'>
{{form.as_p}}
</form>
<form method='POST' name='experienceForm'>
{{form.as_p}}
</form>
<button type="button" id="addMoreButton">Add more</button>
<button type="submit">Save Changes</button>
</div>
Django POST method:
# Get a list of submitted forms
experiences = request.POST.getlist('experienceForm')
for experience in experiences:
# this is how you loop throuh every form
experience.get('company_name)
Your javascript something like:
// clonning his childs as well
let cloneForm = document.querySelector('form[name=experienceForm]').cloneNode(true);
document.querySelector('div#experiencesContainer').appendChild(cloneForm);
// see this https://www.w3schools.com/jsref/met_node_clonenode.asp
Of course this code is not tested but I've done this in several projects before, hope it works!
A simple way would be to request the same view from the "Add", just make sure your form view saves the data when request method is POST.
<form action="{% url 'your-form-url' %}" method="GET">
{% csrf_token %}
<input type="submit" value="Add">
</form>
one other way to repeat forms would be using formsets. Formsets allow you to repeat the same form 'extra' times. Check out the documentation for more about this.
def repeat_form(request):
ExpFormSet = formset_factory(ExperienceForm, extra=3)
#extra defines the no. of forms you want to display
if request.method == 'POST':
formset = ExpFormSet(request.POST, request.FILES)
if formset.is_valid():
# do something with the formset.cleaned_data
#loop through each form in the formser
for form in formset.cleaned_data:
obj = form.save()
else:
formset = ExpFormSet()
return render(request, 'exp_form.html', {'formset': formset})
The corresponding template should be:-
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form.as_p }}
{% endfor %}
</form>
Make sure you add form.management_form. Using the combination of the above might solve your problem of taking and saving several inputs.

Display dynamic data from database in Django

I am using Django and Postgresql as database. I have a HTML page with two fields name and item. I can save the data in the database by clicking on submit. But, I want to show the saved data from database in the HTML page. It means, whenever we load the page, it should show the existing saved data and after submitting new data, the list should be updated. Below is my python code.
models.py
from django.contrib.auth.models import User
from django.db import models
class AllocationPlan(models.Model):
name = models.CharField(max_length=50)
item = models.CharField(max_length=4096)
views.py
class HomePageView(TemplateView):
template_name = "index.html"
def post(self, request, **kwargs):
if request.method == 'POST':
form = AllocationPlanForm(request.POST)
if form.is_valid():
form.save()
return render(request, 'index.html', { 'form': AllocationPlanForm() })
forms.py
from django import forms
from django.forms import ModelForm
from homeapp.models import AllocationPlan
class AllocationPlanForm(ModelForm):
class Meta:
model = AllocationPlan
fields = "__all__"
index.html
<html>
<form method="post">{% csrf_token %}
Name:<br>
<input type="text" name="name" >
<br>
Item:<br>
<input type="text" name="item" >
<br><br>
<input type="submit" value="Submit"/>
</form>
{% for i in form %}
{{ i.name }}
{{ i.item }}
{% endfor %}
</html>
It is returning NONE
Forms in Django are not used to display lists of data. It's merely used to render / validate the form (<form> tag in html). See also the forms doc.
Furthermore, it seems like you're using the TemplateView incorrectly. The post method in your view is only called on a POST request. When you're just viewing the page normally, the template is rendered normally, but since you only add the data to the template in a POST request, the template does not receive a form parameter when loading the view normally (therefore defaulting to None).
According to the TemplateView documentation, you can add the context like so:
class HomePageView(TemplateView):
template_name = 'index.html'
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
# Get the allocation plans from database. Limit to last 10. Adjust to your own needs
context['plans'] = AllocationPlan.objects.all()[:10]
context['form'] = AllocationPlanForm()
return context
def post(self, request, **kwargs):
form = AllocationPlanForm(request.POST)
if form.is_valid():
form.save()
# Handle rest of request here (for example, return the updated page).
As you can see, there is no need to check if request.method == 'POST' in your post method, because Django only calls this method on POST requests. See also dispatch in the docs
To render the data from your database you can now access them in your template as plans:
{% for plan in plans %}
{{ plan.name }}
{{ plan.item }}
{% endfor %}
In your HTML, there is also no need to manually create the form content:
<form method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
This will automatically create the HTML necessary for the form.

Django - prevent CreateView being called twice

What I need to do is access the same CreateView from two different pages. One of those two pages will send along some data that will be inserted into the CreateView form via get_initial(). The button I'm using to access the view is sending some POST data and so the CreateView is immediately thinking everything was successful and going all the way to get_success_url() and then rerouting. Below is what I have. I'm not sure the most elegant way to achieve what I wanted. I was trying to avoid any GET data (making the URLs messy) if possible? This there an elegant way to send POST data to the CreateView upon initialization?
template.html
<form method="POST" action="{% url 'purification:add' %}">
{% csrf_token %}
<input type="hidden" name="sample_id" value="{{ sample_data.id }}">
<button class="btn btn-success" id="add-purification-button" role="button" type="submit">+Add Purification</button>
</form>
view.py
class PurificationCreateView(LoginRequiredMixin, CreateView):
model = Purification
template_name = "add_purification.html"
fields = "__all__"
def get_initial(self):
if self.request.POST.get('sample_id'):
sample = Samples.objects.get(pk=self.request.POST['sample_id'])
return {'sample': sample}
return None
def get_success_url(self):
if "save-and-add-another" in self.request.POST:
return reverse('purification:add')
else:
return reverse('samples:oneSample', kwargs={'sample_id': self.request.POST['sample']})

Include form in multiple templates for GenericForeignKey model (CBV)

To learn more about Django and understand how to program as DRY as possible, I'm building a small website with a couple of features:
Post Blog
Post Image
Post Bookmark
On every object in these APPs, users can post a comment. So I got
a Comment APP with a model that takes a GenericForeignKey. (Properly working)
I want to display a comment form in the Detail view of the Blog, Image, and Bookmark. To keep this dry, I created an inclusion tag
#register.inclusion_tag('comments/add_comment.html')
def show_comment_form(obj):
pk = obj.pk
content_type = ContentType.objects.get_for_model(obj)
form = AddCommentForm()
return {'pk': pk, 'content_type': content_type, 'form': form}
Which uses the following template for rendering the form:
<form action="{% url 'comments:add' pk content_type %}" method="post">
<div class="create-blog-container">
<div class="box-top">{% trans "Leave your comment behind" %}...</div>
<div class="box-content">
{% csrf_token %}
{{ form.as_div }}
</div>
<div class="box-footer">
<input type="submit" class="btn btn-green" value="{% trans 'Add comment' %}" />
</div>
</form>
When adding
{% show_comment_form blog %}
In the detail template of the blog, it renders the form with the right parameters. When I hit the SUBMIT button, the form is handled in the following Add view (currently just takes the form, validates, and adds it to the database via a service).
class Add(LoginRequiredMixin, View):
model = Comment
def post(self, *args, **kwargs):
content_type = self.kwargs.get('content_type')
pk = self.kwargs.get('pk')
form = AddCommentForm(self.request.POST)
if form.is_valid():
content = form.cleaned_data.get('content')
comments.services.comment.add(content_type, pk, self.request.user, content)
messages.add_message(self.request, messages.SUCCESS, _("Your comment has been posted"))
else:
print(form.errors)
return HttpResponseRedirect(reverse('blogs:detail', kwargs={'pk': pk}))
But this solution does not render back the form with validation errors. I went back to the basics to create a 'hard-coded' form in the blog's detail view, but it's not flexible nor DRY.
Can somebody give me a push in the right direction how to convert this form (via an inclusion tag, or other suggested method) into a DRY and flexible form to post a comment on a generic content_type?
In order to complete DRY I suggest (as you mentioned) implementing commenting for Django contenttypes and mixing it with AJAX.
Another one for your case it's possible to pass validated form to you inclusion tag (show_comment_form)
#register.inclusion_tag('comments/add_comment.html')
def show_comment_form(obj, form=None):
pk = obj.pk
content_type = ContentType.objects.get_for_model(obj)
form = form or AddCommentForm()
return {'pk': pk, 'content_type': content_type, 'form': form}
Change your inclusion tag, so it will post comment for on different URL, depending on object type. Also change it, so it won't make new form if there is already comment_form in current context. Example:
#register.inclusion_tag('comments/add_comment.html', takes_context=True)
def show_comment_form(context, obj):
pk = obj.pk
content_type = ContentType.objects.get_for_model(obj)
form = context.get('comment_form', AddCommentForm())
url_id = "%s:%s:comment:add" % (content_type.app_label, content_type.model)
return {'pk': pk, 'content_type': content_type, 'form': form, url_id}
template:
{# content_type may be obsolete now #}
<form action="{% url url_id pk content_type %}" method="post">
<div class="create-blog-container">
<div class="box-top">{% trans "Leave your comment behind" %}...</div>
<div class="box-content">
{% csrf_token %}
{{ form.as_div }}
</div>
<div class="box-footer">
<input type="submit" class="btn btn-green" value="{% trans 'Add comment' %}" />
</div>
</form>
Create view mixin, that will add to your view handling comment creation (or form validation), create new views for each of your content types from normal view for that content and your new mixin and register that view on separate URL (name that url in way that you're creating url_id in your template tag).
It's dry, flexible and doesn't require lot of changes.

A way to render content to a base template

I have a base template which contains header, footer and a block "content" which then I override in different CBVs.
There is little "user-space" divinside a header where I want to keep user's info in case he is logged in and a login form if he is a guest.
Right now the only way that comes in mind is to create a django app and use it as a tag. But I think there is a better solution. Because as far as I know tags can slow down django project in future.
Also I think that maybe I would like to manipulate this div from a view which renders a child template. For example I calculate and display some value on a page but also I want to display it in a "user-space" div as well. I think this can be achieved by pushing data to the user's session and then using this while rendering "user-space" div from another code.
Assuming you have django.contrib.auth.user in your INSTALLED_APPS, you can access the user's login status using user.is_authenticated():
{% if user.is_authenticated %}
<div>Welcome back, {{ user.username }} | <a href='/logout/'>Logout</a></div>
{% else %}
<div>
<form action='/login/' method='POST'>
{{ csrf_token }}
{{ login_form }}
<input type='submit'>
</form>
</div>
{% endif %}
Edit:
In response to your comment:
Let's suppose client does a POST request by which we calculate some number - total price. And I need to display it in that div.
As documented
Define your view:
from django.shortcuts import render
from .forms import MyForm
def simple_view(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = MyForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
result = form.cleaned_data['some_input'] * 50
return render(request, 'my_template.html', {'result': result})
# if a GET (or any other method) we'll create a blank form
else:
form = MyForm()
return render(request, 'my_other_template.html', {'form': form})
Display result in your template:
<!-- my_template.html -->
<div>{{ result }}</div>
Maybe you need to be more specific with your question, this is what I think you are looking for though. You should put logic like you are describing into a view, not a template.

Categories