I am working on a blog in Django and i am trying to update the image of my model BlogPost using a ModelForm.
Initialy , when creating the post, the image is being uploaded with no problems in media/posts. However , nothing happens when trying to update the existing image with another one ( or to add an image to a post already created without one).
I have found a solution online for this issue and that was to override the save() method for the model. I did that but still nothing seems to happen. Clearly, I am doing something wrong.
My code below:
views.py:
def blog_post_update_view(request,slug):
obj = get_object_or_404(BlogPost.objects.filter(user=request.user), slug=slug)
form = BlogPostModelForm(request.POST or None, instance=obj)
if form.is_valid():
form.save()
print(obj.image)
return redirect(obj.get_absolute_url()+"/detail/")
template_name = 'blog/form.html'
context = {"title": f"Update {obj.title}", "form": form}
return render(request, template_name, context)
models.py:
class BlogPost (models.Model):
# id = models.IntegerField() # pk
user= models.ForeignKey(User, default=1, null=True, on_delete=models.SET_NULL)
image=models.ImageField( upload_to='posts/',blank=True,null=True)
title=models.CharField(max_length=120)
slug= models.SlugField() # hello world -> hello-world
content=models.TextField(null=True,blank=True)
publish_date=models.DateTimeField(auto_now=False, auto_now_add=False, null=True, blank=True)
timestamp=models.DateTimeField(auto_now_add=True)
updated=models.DateTimeField(auto_now=True)
objects = BlogPostManager()
class Meta:
ordering = ['-publish_date', '-updated', '-timestamp']
def get_content_length(self):
return len(self.content)
def get_absolute_url(self):
return f"/blog/{self.slug}"
def get_edit_url(self):
return f"{self.get_absolute_url()}/edit"
def get_delete_url(self):
return f"{self.get_absolute_url()}/delete"
def save(self, *args, **kwargs):
try:
this = BlogPost.objects.get(id=self.id)
if this.image!= self.image:
this.image.delete()
except: pass
super(BlogPost, self).save(*args, **kwargs)
def __str__(self):
return self.title
forms.py:
class BlogPostModelForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['title','image', 'slug', 'content', 'publish_date']
def clean_title(self, *args, **kwargs):
instance = self.instance
print('instance is: ',instance)
title = self.cleaned_data.get('title')
qs = BlogPost.objects.filter(title__iexact=title)
if instance is not None:
qs = qs.exclude(pk=instance.pk) # id=instance.id
if qs.exists():
raise forms.ValidationError("This title has already been used. Please try again.")
return title
blog/templates/form.html:
<!doctype html>
{% extends "blog/base.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% block head_title %}
{{title}}
{% endblock %}
{% block content %}
{% if title %}
<h1>{{ title }}</h1>
{% endif %}
<form method='POST' enctype="multipart/form-data" action='.'> {% csrf_token %}
{{ form|crispy}}
<button type='submit'>Send</button>
</form>
{% endblock %}
your view should be like:
def blog_post_update_view(request,slug):
obj = get_object_or_404(BlogPost.objects.filter(user=request.user), slug=slug)
if request.method == "POST":
form = BlogPostModelForm(request.POST, request.FILES, instance=obj)
if form.is_valid():
form.save()
return redirect(obj.get_absolute_url()+"/detail/")
form = BlogPostModelForm(instance=obj)
template_name = 'blog/form.html'
context = {"title": f"Update {obj.title}", "form": form}
return render(request, template_name, context)
Related
I'm trying to make inline form by using inlineformset_factory but my Image object is not getting saved
models:
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
availability = models.IntegerField()
price = models.DecimalField(max_digits=5, decimal_places=2)
def __str__(self):
return self.name
class Image(models.Model):
file = models.ImageField(upload_to="products_images/", default="static/default.png")
uploaded = models.DateTimeField(auto_now_add=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
views:
def CreateNewProductView(request):
context = {}
ProductObj = None
form=ProductForm()
if request.method=='POST':
form = ProductForm(request.POST)
if form.is_valid():
ProductObj = form.save()
print('form is valid, product has been created')
else:
print("form is not valid")
ImageFormset = inlineformset_factory(Product, Image, fields=('file',), extra=1, can_delete=False)
if request.method=='POST':
formset = ImageFormset(request.POST, request.FILES, instance=ProductObj)
if formset.is_valid():
formset.save()
print('formset is valid, product has been created')
else:
print("formset is not valid")
else:
formset = ImageFormset(instance=ProductObj)
if form.is_valid() and formset.is_valid():
return redirect('home')
context = {'form': form, 'formset':formset}
return render(request, 'Ecommerce/test.html', context)
template test.html
{% extends 'base.html' %}
{% block content %}
<form method="POST" action="" id="image-form" style="padding-top:10px;">
{% csrf_token %}
{{form.as_p}}
{{formset}}
{{formset.management_form}}
<button type="submit">submit</button>
</form>
{% endblock content %}
In console I can see "formset is valid, product has been created"
When I printed (request.FILES) i saw <MultiValueDict: {}>. Should it be like that ? In django admin pannel there is no Image objects
What am I doing wrong ?
Add this to your HTML form tag to send files to the server:
enctype="multipart/form-data"
Easy to forget.
Your form will then look like:
<form method="POST" enctype="multipart/form-data" action="" id="image-form" style="padding-top:10px;">
...
Link to Django-docs
I am having 2 issues, one if you submit and click back and then submit again it duplicates the instance in the database - in this case Household. In addition it is saving the parent 'Household' without the child 'Applicants' despite me setting min_num=1
can someone point me in the right direction to resolve this issue.
Many thanks in advance
class Application(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
application_no = models.CharField(max_length=100, unique=True, default=create_application_no)
created_date = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
class HouseHold(models.Model):
name = models.CharField(max_length=100)
application = models.ForeignKey(Application, on_delete=models.CASCADE)
no_of_dependents = models.PositiveIntegerField(default=0)
class Applicant(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
household = models.ForeignKey("HouseHold", on_delete=models.CASCADE)
forms.py
class ApplicationForm(ModelForm):
class Meta:
model = Application
fields = (
"name",
)
class ApplicantForm(ModelForm):
class Meta:
model = Applicant
fields = [
"household",
"first_name",
"last_name"
]
class HouseHoldForm(ModelForm):
class Meta:
model = HouseHold
fields = [
'name',
'application',
'no_of_dependents'
]
def __init__(self, application_id=None, *args, **kwargs):
super(HouseHoldForm, self).__init__(*args, **kwargs)
self.fields['name'].label = 'House Hold Name'
if application_id:
self.fields['application'].initial = application_id
self.fields['application'].widget = HiddenInput()
ApplicantFormset = inlineformset_factory(
HouseHold, Applicant, fields=('household', 'first_name', 'last_name'), can_delete=False, extra=1, validate_min=True, min_num=1)
views.py
class HouseHoldCreateView(LoginRequiredMixin, generic.CreateView):
model = models.HouseHold
template_name = "households/household_create.html"
form_class = HouseHoldForm
def get_parent_model(self):
application = self.kwargs.get('application_pk')
return application
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['application'] = models.HouseHold.objects.filter(application_id=self.kwargs['application_pk']).last()
context['house_hold_formset'] = ApplicantFormset(self.request.POST, instance=self.object)
else:
context['application'] = models.Application.objects.get(id=self.kwargs['application_pk'])
context['house_hold_formset'] = ApplicantFormset()
return context
def get_form_kwargs(self):
kwargs = super(HouseHoldCreateView, self).get_form_kwargs()
print(kwargs)
kwargs['application_id'] = self.kwargs.get('application_pk')
return kwargs
def form_valid(self, form):
context = self.get_context_data()
applicants = context['house_hold_formset']
with transaction.atomic():
self.object = form.save()
if applicants.is_valid():
applicants.instance = self.object
applicants.save()
return super(HouseHoldCreateView, self).form_valid(form)
def get_success_url(self):
if 'addMoreApplicants' in self.request.POST:
return reverse('service:household-create', kwargs={'application_pk': self.object.application.id})
return reverse('service:household-list', kwargs={'application_pk': self.object.application.id})
I had a similar problem, I solved it by adding the post() method to the view. The example is an UpdateView but the usage is the same.
(the indentation is not correct but that's what stackoverflow's editor let me do, imagine all methods are 4 spaces to the right)
class LearnerUpdateView(LearnerProfileMixin, UpdateView):
model = User
form_class = UserForm
formset_class = LearnerFormSet
template_name = "formset_edit_learner.html"
success_url = reverse_lazy('home')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
learner = User.objects.get(learner=self.request.user.learner)
formset = LearnerFormSet(instance=learner)
context["learner_formset"] = formset
return context
def get_object(self, queryset=None):
user = self.request.user
return user
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
user = User.objects.get(learner=self.get_object().learner)
formsets = LearnerFormSet(self.request.POST, request.FILES, instance=user)
if form.is_valid():
for fs in formsets:
if fs.is_valid():
# Messages test start
messages.success(request, "Profile updated successfully!")
# Messages test end
fs.save()
else:
messages.error(request, "It didn't save!")
return self.form_valid(form)
return self.form_invalid(form)
Keep in mind that to save the formset correctly you have to do some heavy lifting in the template as well. I'm referring to the hidden fields which can mess up the validation process. Here's the template corresponding to the view posted above:
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
{{ learner_formset.management_form}}
{% for form in learner_formset %}
{% if forloop.first %}
{% comment %} This makes it so that it doesnt show the annoying DELETE checkbox {% endcomment %}
{% for field in form.visible_fields %}
{% if field.name != 'DELETE' %}
<label for="{{ field.name }}">{{ field.label|capfirst }}</label>
<div id="{{ field.name }}" class="form-group">
{{ field }}
{{ field.errors.as_ul }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% for field in form.visible_fields %}
{% if field.name == 'DELETE' %}
{{ field.as_hidden }}
{% else %}
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
<input class="btn btn-success" type="submit" value="Update"/>
Additional reading :
https://medium.com/#adandan01/django-inline-formsets-example-mybook-420cc4b6225d
Save formset in an UpdateView
Inspired by Beikini
I have solved it using the create View
class HouseHoldCreateView(LoginRequiredMixin, generic.CreateView):
model = HouseHold
template_name = "households/household_create3.html"
form_class = HouseHoldForm
def get_parent_model(self):
application = self.kwargs.get('application_pk')
return application
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['application'] = HouseHold.objects.filter(
application_id=self.kwargs['application_pk']).last()
context['house_hold_formset'] = ApplicantFormset(self.request.POST)
else:
context['application'] = Application.objects.get(id=self.kwargs['application_pk'])
context['house_hold_formset'] = ApplicantFormset()
return context
def get_form_kwargs(self):
kwargs = super(HouseHoldCreateView, self).get_form_kwargs()
kwargs['application_id'] = self.kwargs.get('application_pk')
return kwargs
def form_valid(self, form):
context = self.get_context_data()
applicants = context['house_hold_formset']
application_id = self.kwargs['application_pk']
household_form = self.get_form()
if form.is_valid() and applicants.is_valid():
with transaction.atomic():
self.object = form.save()
applicants.instance = self.object
applicants.save()
messages.success(self.request, 'Applicant saved successfully')
return super(HouseHoldCreateView, self).form_valid(form)
else:
messages.error(self.request, 'please add an applicant to the household')
return self.form_invalid(form)
def get_success_url(self):
return reverse('service:household-list', kwargs={'application_pk': self.object.application.id})
I created a form to update a User's profile, however when I run it, there are no errors, but when I try to open up the page, the header appears but the UpdateBioForm does not appear. Secondly, I was wondering how you would create a large textbox to store someone's biography.
Models.py
class UserProfile(models.Model):
user = models.OneToOneField(User)
biography = models.CharField(max_length = 255, default = '')
city = models.CharField(max_length=100, default = '')
website = models.URLField(default='')
image = models.ImageField(upload_to='profile_image', blank=True)
def setdefault(self, default_path='/profile_image/Default.jpg'):
if self.image:
return self.image
return default_path
def __str__(self):
return self.user.username
Forms.Py
class UpdateBioForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = (
'biography',
'city',
'website'
)
def save(self, commit=True):
savedBio = super(UpdateBioForm, self).save(commit=False)
savedBio.biography = self.cleaned_data['biography']
savedBio.city = self.cleaned_data['city']
savedBio.website = self.cleaned_data['website']
if commit:
savedBio.save()
return savedBio
Views.py
def update_bio(request):
if request.method == 'POST':
form = UpdateBioForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
return redirect('/')
else:
form = UpdateBioForm(instance=request.user)
args = {'form':form}
return render(request, 'accounts/update_bio.html')
urls.py
url(r'^profile/updatebio/$',views.update_bio, name='update_bio'),
update_bio.html
{% extends 'base.html' %}
{% block body %}
<div class="container">
<h1>Update Biography</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
</div>
{% endblock %}
You are not passing any context to your render() method - you define args but don't do anything with that variable. Change it to:
args = {'form':form}
return render(request, 'accounts/update_bio.html', context=args) # <-- You're missing context
I'm having two issues:
EDIT:
I made some tweaks and partially resolved the issue. But now, when I submit a new blog post form it takes me to the wrong url and no matter what url I try, I can't find the full post. When I click on a link to the full post I get a Type Error "Object "Post" is not iterable". Category "progresstracker" works perfectly, "blogtopics/.." doesn't.
Here's the latest code:
views.py
def pt_detail(request, slug, category):
post = get_object_or_404(Post, slug=slug, category__slug=category)
template = CATEGORY_TEMPLATES.get(post.category.slug)
return render(request, template, {'post': post})
def bt_detail(request, slug, category):
post = get_object_or_404(Post, slug=slug, category__slug=category)
template = CATEGORY_TEMPLATES.get(post.category.slug)
return render(request, template, {'post': post})
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.published_date = timezone.now()
post.save()
if post.category.slug == 'blog-topics':
return redirect('bt_detail', slug=post.slug, category=post.category)
else:
return redirect('pt_detail', slug=post.slug, category=post.category)
else:
form = PostForm()
return render(request, 'blog/post_edit.html', {'form': form})
def post_edit(request, slug, category):
post = get_object_or_404(Post, slug=slug, category__slug=category)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.published_date = timezone.now()
post.save()
if post.category.slug == 'blog-topics':
return redirect('bt_detail', slug=post.slug, category=post.category)
else:
return redirect('pt_detail', slug=post.slug, category=post.category)
else:
form = PostForm(instance=post)
return render(request, 'blog/post_edit.html', {'form': form})
urls.py
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^home/$', views.home, name='home'),
url(r'^blogtopics/computer-science/$', views.compsci, name='computer-science'),
url(r'^blogtopics/data-science/$', views.datasci, name='data-science'),
url(r'^blogtopics/other/$', views.other, name='other'),
url(r'^blogtopics/$', views.blogtopics, name='blogtopics'),
url(r'^resources/$', views.resources, name='resources'),
url(r'^new/$', views.post_new, name='post_new'),
url(r'^progresstracker/$', views.progresstracker, name='progresstracker'),
url(r'^blogtopics/(?P<category>[\w-]+)/(?P<slug>[\w-]+)/$', views.bt_detail, name='bt_detail'),
url(r'^blogtopics/(?P<category>[\w-]+)/(?P<slug>[\w-]+)/edit/$', views.post_edit, name='post_edit'),
url(r'^(?P<category>[\w-]+)/(?P<slug>[\w-]+)/$', views.pt_detail, name='pt_detail'),
url(r'^(?P<category>[\w-]+)/(?P<slug>[\w-]+)/edit/$', views.post_edit, name='post_edit'),
]
computerscience.html
{% extends 'blog/base.html' %}
{% block nav %}
<li>Home</li>
<li>Progress Tracker</li>
<li class="dropdown">
<a class="active" href="/blogtopics" class="dropbtn">Blog Topics</a>
<div class="dropdown-content">
Computer Science
Data Science
Other
</div>
</li>
<li>Resources</li>
{% endblock %}
{% block content %}
<div id="content">
<div class="padding">
<p style="padding-top: 50px">
<div class="post_list">
{% for post in post %}
<h3>{{ post.title }}</h3>
<p>{{ post.published_date }}</p>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
models.py
class Post(models.Model):
title = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=100, unique=True)
body = models.TextField()
posted = models.DateField(db_index=True, auto_now_add=True)
category = models.ForeignKey('books.Category', related_name='%(class)s_slug')
created_date = models.DateTimeField(
default = timezone.now)
published_date = models.DateTimeField(
blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __unicode__(self):
return '%s' % self.title
def get_absolute_url(self):
return "/%s/%s/" % (self.category, self.slug)
def get_absolute_url_bt(self):
return "/blogtopics/%s/%s/" % (self.category, self.slug)
It looks like you have a couple things going on here. First you said that any category will get sent to a blank computerscience page. This tells me that your if statement is always resulting in false so it's trying to render the computerscience.html template. The pt_detail view has a single Post object(ptpost) in the context. The compsci view uses the same computerscience.html template to render a QuerySet of Post objects using posts in the context. You can't do both. Since your template is expecting posts in your context and only finds ptpost it can't render anything, hence the blank page.
As far as using category to determine which template to use, you could use a series of elif statements, or you could do something like this:
CATEGORY_TEMPLATES = {
'progresstracker': 'blog/pt_detail.html',
'anothercategory': 'blog/ac_detail.html',
'yetanothercategory': 'blog/yac_detail.html',
}
def pt_detail(request, slug, category):
ptpost = get_object_or_404(Post, slug=slug, category__slug=category)
template = CATEGORY_TEMPLATES.get(ptpost.category.slug, 'blog/default_detail.html')
return render(request, template, {'ptpost': ptpost})
This will select the category from the CATEGORY_TEMPLATES dictionary and use blog/default_detail.html as the default if the current category isn't in the dict.
I have issue in Django,I am trying to make form which is working for admin side but I want to saw same form in front end this think done some mistake so its not display.
model.py class
class Product(models.Model):
title = models.CharField(max_length=120)
description = models.TextField(null=True, blank=True)
category = models.ManyToManyField(Category, null=True, blank=True)
price = models.DecimalField(decimal_places=2, max_digits=100, default=29.99)
sale_price = models.DecimalField(decimal_places=2, max_digits=100,\
null=True, blank=True)
slug = models.SlugField(unique=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
active = models.BooleanField(default=True)
update_defaults = models.BooleanField(default=False)
def __unicode__(self):
return self.title
class Meta:
unique_together = ('title', 'slug')
def get_price(self):
return self.price
def get_absolute_url(self):
return reverse("single_product", kwargs={"slug": self.slug})
my admin.py
from .models import Product, ProductImage, Variation, Category
admin.site.register(ProductImage)
admin.site.register(Variation)
admin.site.register(Category)
from products.models import Product
admin.site.register(Product)
html file
{% extends 'base.html' %}
{% load staticfiles %}
{% block head_title %}
Products ||
{% endblock %}
{% block styles %}
.jumbotron {
/*color: red;*/
}
{% endblock %}
{# Comments here #}
{% block content %}
<div class='col-sm-6 col-sm-offset-3' style='margin-top: 50px;'>
<h1>{{ form_title }}</h1>
<form method="post" enctype="multipart/form-data">
<div style="background-color:#f2f2f2" ><br />
<div style="width:25%; margin:0px auto;">
{% csrf_token %}
{{ form.as_table}}
<input type='submit' value='{% if submit_btn %}{{ submit_btn }}{% else %}Submit{% endif %}' class='btn btn-default' />
</div>
</div>
</form>
</div>
{% endblock %}
view.py
from django.shortcuts import render, Http404
from marketing.forms import EmailForm
from marketing.models import MarketingMessage, Slider
from .models import Product, ProductImage
def search(request):
try:
q = request.GET.get('q')
except:
q = None
if q:
products = Product.objects.filter(title__icontains=q)
context = {'query': q, 'products': products}
template = 'products/results.html'
else:
template = 'products/home.html'
context = {}
return render(request, template, context)
def home(request):
sliders = Slider.objects.all_featured()
products = Product.objects.all()
template = 'products/home.html'
context = {
"products": products,
"sliders": sliders,
}
return render(request, template, context)
def all(request):
products = Product.objects.all()
context = {'products': products}
template = 'products/all.html'
return render(request, template, context)
def deals(request):
products = Product.objects.all()
template = 'deals.html'
context = {
"products": products,
}
return render(request, template, context)
def single(request, slug):
try:
product = Product.objects.get(slug=slug)
#images = product.productimage_set.all()
images = ProductImage.objects.filter(product=product)
context = {'product': product, "images": images}
template = 'products/single.html'
return render(request, template, context)
except:
raise Http404
You should do like this:
def my_view(request):
form = my_form()
if request.method == "POST":
form = my_form(request.POST, request.FILES)
if form.is_valid():
#do something with cleaned_data
form.save()
else:
return render_to_response("your_template.html", {'form':form}, context_instance=RequestContext(request))
else:
form = my_form()
return render_to_response("your_template.html", {'form':form}, context_instance=RequestContext(request))
and in your template use like:
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form.as_p}}
</form>