Django Class-based ViewCreate and ViewUpdate file upload - python

I have a project where I wish to upload an image with the Django auto generated class-based views, and it works on the admin side, but I don't know what I'm missing to make it work from an HTML page. I've searched the web with a little luck or not enough clarification.
So maybe someone can tell me what I'm missing. Here is my code:
settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'project/app/media_cdn')
models.py
from django.db import models
from django.core.urlresolvers import reverse
class Article(models.Model):
title = models.CharField(max_length = 200)
...
thumbnail = models.FileField(null = True, blank = True)
...
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('articles_detail', kwargs={'pk': self.pk})
class Meta:
ordering = ['-pk']
views.py
from django.shortcuts import render
from app.models import Article
from django.views.generic import *
from django.core.urlresolvers import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin
# Create your views here.
def index(request):
return render(request, 'index.html')
class ArticleList(ListView):
model = Article
class ArticleDetail(DetailView):
model = Article
class ArticleCreate(LoginRequiredMixin, CreateView):
model = Article
fields = ['title', 'description', 'abstract', 'thumbnail', 'author', 'category', 'publishDate']
class ArticleUpdate(LoginRequiredMixin, UpdateView):
model = Article
fields = ['title', ..., 'thumbnail', ...]
class ArticleDelete(LoginRequiredMixin, DeleteView):
model = Article
success_url = reverse_lazy('articles_list')
urls.py
from django.conf.urls import url
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static
from app import views, auth
urlpatterns = [
url(r'^admin/',admin.site.urls),
...
url(r'^articles/(?P<pk>[0-9]+)/$', views.ArticleDetail.as_view(), name = 'articles_detail'),
url(r'^articles/create/$', views.ArticleCreate.as_view(), name = 'articles_create'),
url(r'^articles/update/(?P<pk>[0-9]+)/$', views.ArticleUpdate.as_view(), name = 'articles_update'),
url(r'^articles/delete/(?P<pk>[0-9]+)/$', views.ArticleDelete.as_view(), name = 'articles_delete'),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
article_form.html
{% extends 'layout.html' %}
{% block content %}
<form method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
{% endblock %}
As much as I was able to gather, I managed to follow all the steps but I still can't get the thumbnail to update when uploading from articles/create/ and articles/update/.../
Thanks in advance.

Related

Django upload multiple images per post

I want to let the user upload multiple images per post. Similarly to an e-commerce platform with multiple images per product. But till now the images are not sent to the database.
That's my code so far:
models.py:
class Project(models.Model):
title = models.CharField(max_length=200)
describtion = models.TextField(null=True, blank=True)
class ProjectImage(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
image = models.FileField(upload_to="products")
forms.py:
class ProjectForm(ModelForm):
image = forms.ImageField(widget=ClearableFileInput(attrs={'multiple':True}))
class Meta:
model = Project
fields = ['title', 'describtion']
views.py:
def createProject(request):
form = ProjectForm()
if request.method == 'POST':
form = ProjectForm(request.POST)
images = request.FILES.getlist('image')
if form.is_valid():
project = form.save()
for i in images:
ProjectImage(project=project, image=i).save()
context = {'form':form}
return render(request, 'projects/project_form.html', context)
project_form.html:
<form class="form" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="form__field">
<label for="formInput#text">{{field.label}}</label>
{{field}}
</div>
{% endfor %}
<input type="submit" name="" id="">
</form>
settings.py:
STATIC_URL = '/static/'
MEDIA_URL = '/images/'
STATICFILES_DIRS = [
BASE_DIR / 'static'
]
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/images')
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
project urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('projects.urls')),
]
app urls.py
urlpatterns = [
path("", views.createProject, name="create-project")
]
Issue:
You have made ProjectForm which relates to Project model, but the image field is in ProjectImage model. So, image field is not even passing to the template and you also haven't passed it in fields=['title','describtion'] in ProjectFrom.
You haven't made configurations for saving the media files in project's urls.py.
Solution:
You should make two forms in forms.py, first ProjectForm which will get the data for Project model and second ProjectImageForm which will get the list of images, then using request.FILES.getlist('image') you can save images which relates to a particular instance one by one in loop as you tried to save.
You should make media configurations in project's urls.py
Try Below Code:
forms.py
from django import forms
from django.forms import ClearableFileInput
from .models import Project, ProjectImage
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['title', 'describtion']
class ProjectImageForm(forms.ModelForm):
class Meta:
model = ProjectImage
fields = ['image']
widgets = {
'image': ClearableFileInput(attrs={'multiple': True}),
}
views.py
from django.http import HttpResponse
from django.shortcuts import redirect, render
from .forms import ProjectImageForm, ProjectForm
from .models import Project, ProjectImage
def createProject(request):
form = ProjectForm()
form2 = ProjectImageForm()
if request.method == 'POST':
form = ProjectForm(request.POST)
form2 = ProjectImageForm(request.POST, request.FILES)
images = request.FILES.getlist('image')
if form.is_valid() and form2.is_valid():
title = form.cleaned_data['title']
describ = form.cleaned_data['describtion']
print(title, describ)
project_instance = Project.objects.create(
title=title, describtion=describ)
print('-------------------------------------------')
print(project_instance)
print('-------------------------------------------')
for i in images:
ProjectImage.objects.create(project=project_instance, image=i)
return redirect('thanks')
context = {'form': form, 'form2': form2}
return render(request, 'projects/project_form.html', context)
def thanks(request):
return HttpResponse('<h1>Form saved.</h1>')
project_form.html or template file:
<form class="form" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{form.title.label_tag}}
{{form.title}}
<br><br>
{{form.describtion.label_tag}}
{{form.describtion}}
<br><br>
{{form2.image.label_tag}}
{{form2.image}}
<br><br>
<input type="submit" name="" id="">
</form>
project's urls.py
from django.conf.urls.static import static
from django.conf import settings
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('projects.urls'))
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
app's urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('home.urls'))
]
Your models.py and settings.py can be remain same, but it's recommended to use
MEDIA_URL = 'media/' and MEDIA_ROOT = os.path.join(BASE_DIR, 'media/'), then you should make nested folders inside it to save images or any files.
Note: You should always return HttpResponseRedirect after dealing with POST data, the tip is not specific to Django, it's a good practice in general as stated in the tutorial4.
Note: Function based views are generally written in snake_case not camelCase, you may change it to create_project from createProject.
Note: Add / at the end of upload_to as upload_to='products/' in FileField in ProjectImage model.

For some reason {{ post.message }} is not displaying the modle

I am making a simple site to experiment with manipulating user data. I have a form where the user enters in some info and once they hit submit they get redirected to a new page. On this new page, the info they entered is supposed to be displayed. I am under the impression that you use this {{ Modle_Name.Fild_name}} to inject the info into the HTML. However, it is not working. If any of y'all have a solution I would much appreciate it.
sucseus.html
{% extends "base.html" %}
{% block content %}
<h1>success</h1>
<h2>{{ post.message }}</h2>
{% endblock %}
views.py
from django.shortcuts import render
from django.urls import reverse_lazy
from django.views import generic
from . import forms
from forums_simple.models import Post
# Create your views here.
class Form(generic.CreateView):
model = Post
template_name = 'forums_simple/form.html'
fields = ['message']
success_url = reverse_lazy('forums:sucsseus')
class Sucsessus_view(generic.TemplateView):
template_name = 'forums_simple/sucseus.html'
model = Post
models.py
from django.db import models
# Create your models here.
class Post(models.Model):
message = models.TextField(blank=True, null=False)
created_at = models.DateTimeField(auto_now=True)
You need to pass it as a context. The best way to do it is via DetailView.
class Sucsessus_view(generic.DetailView):
template_name = 'forums_simple/sucseus.html'
model = Post
urls:
urlpatterns = [
...
path('post/<int:pk>/', Sucsessus_view.as_view(), name='sucsseus'),
...
]
here are my URLs
from django.contrib import admin
from django.urls import path, include
from . import views
app_name = 'forums'
urlpatterns = [
path('sucseuss/<int:pk>/', views.Sucsessus_view.as_view(), name='working'),
path('forum/', views.Form.as_view(), name='form')
]

NoReverseMatch at /add_post

I have been working on a blog site using django and I made a way to add post within the home page without going to the admin page but when I post using the new way I get this error
This is my models.py file
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=255)
title_tag = models.CharField(max_length=255)
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.TextField(max_length=3500)
def __str__(self):
return (self.title + " | " + str(self.author))
def get_absolute_url(self):
return reverse("article-view", args=(str(self.id)))
This is the views.py file
from django.views.generic import ListView, DetailView, CreateView
from .models import Post
class HomeView(ListView):
model = Post
template_name = "home.html"
class ArticleDetailView(DetailView):
model = Post
template_name = "detail_view.html"
class AddPostView(CreateView):
model = Post
template_name = "add_post.html"
fields = "__all__"
This is the polls/urls.py
from django.urls import path
from .views import HomeView, ArticleDetailView, AddPostView
urlpatterns = [
path('', HomeView.as_view(), name='home'),
path('article/<int:pk>', ArticleDetailView.as_view(), name='article-view'),
path('add_post/', AddPostView.as_view(), name='add_post'),
]
This is the add_post.html file
{% extends 'base.html' %}
{% block content %}
<head>
<title>Adding Post</title>
</head>
<h1>Add Blog Posts</h1>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-secondary">Post</button>
</form>
{% endblock %}
Thank you.
Okay, so it looks like this is caused by the model's get_absolute_url reverse args=(). I changed the below code in models.py from:
def get_absolute_url(self):
return reverse("article-view", args=(str(self.id)))
Into
def get_absolute_url(self):
return reverse("article-view", args=[self.id])
The problem seems to be args=(), it is iterating over the str(self.id). So id=10 would actually be returned as a tuple (1,0). I also removed the str() around the self.id since the URL takes in an int.

Adding UUIDs to my Django app yields a NoReverseMatch error

I have a books app using a UUID with a listview of all books and detailview of individual books. I keep getting the following error message:
NoReverseMatch at /books/
Reverse for 'book_detail' with arguments '('/books/71fcfae7-bf2d-41b0-abc8-c6773930a44c',)' not found. 1 pattern(s) tried: ['books/(?P<pk>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$']
Here is the models.py file:
# books/models.py
import uuid
from django.db import models
from django.urls import reverse
class Book(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
author = models.CharField(max_length=200)
price = models.DecimalField(max_digits=6, decimal_places=2)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('book_detail', args=[str(self.id)])
The urls.py file where I'm using to convert the id from the model to a uuid.
# books/urls.py
from django.urls import path
from .views import BookListView, BookDetailView
urlpatterns = [
path('', BookListView.as_view(), name='book_list'),
path('<uuid:pk>', BookDetailView.as_view(), name='book_detail'),
]
The top-level urls.py file looks like this and adds a books/ route.
# urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', include('books.urls')),
The views.py file:
from django.views.generic import ListView, DetailView
from .models import Book
class BookListView(ListView):
model = Book
context_object_name = 'book_list'
template_name = 'books/book_list.html'
class BookDetailView(DetailView):
model = Book
context_object_name = 'book'
template_name = 'books/book_detail.html'
And the relevant templates file.
<!-- templates/book_detail.html -->
{% extends '_base.html' %}
{% block content %}
{% for book in book_list %}
<div>
<h2>{{ book.title }}</h2>
</div>
{% endfor %}
{% endblock content %}
I believe I'm implementing this correctly but the URL is not liking my UUID. What is amiss?
The problem is not "adding uuid". The problem is that you are doing the URL reversal twice: once in get_absolute_url and once in the {% url %} tag. Use one or the other, not both.
Either:
{{ book.title }}
Or:
{{ book.title }}

Django Form Not Rendering HTML from class HomeForm

For some reason I cannot get my Django form to render. I'm not sure what I'm missing.
I'm trying to get the class HomeForm(forms.Form) to render on the myaccount/change.html page and have been unsuccessful.
I'm new to Django so I might be missing a small detail somewhere but I thought I got everything covered from the djago documentation.
If anyone can help me out it would be gladly appreciated. Thanks!
users/forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django import forms
from .models import CustomUser
class HomeForm(forms.Form):
post = forms.CharField()
your_name = forms.CharField(label='Your name', max_length=100)
class CustomUserCreationForm(UserCreationForm):
# job_title = forms.IntegerField(label='2 + 2', label_suffix=' =')
# yes = forms.IntegerField(required=True)
tos_check = forms.BooleanField(required=True, label='I have read and agree to the Terms of Service.')
age_check = forms.BooleanField(required=True, label='I am 21 years of age or older.')
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = ('username', 'email')
help_texts = {
'username': '',
'email': '',
# 'password1': 'None',
# 'password2': 'Test'
}
class CustomUserChangeForm(UserChangeForm):
tos_checktwo = forms.BooleanField(required=True, label='I have read and agree to the Terms of Service.')
class Meta(UserChangeForm.Meta):
model = CustomUser
fields = ('username', 'email', 'tos_checktwo')
# class Meta:
# model = CustomUser
# fields = ('username', 'email', 'tos_check',)
users/views.py
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
from django.shortcuts import render
from django.template import loader
from django.urls import reverse_lazy
from django.views import generic
from django import forms
from .forms import CustomUserCreationForm
from .forms import HomeForm
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = HomeForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/home/')
# if a GET (or any other method) we'll create a blank form
else:
form = HomeForm()
return render(request, 'myaccount/change.html', {'form': form})
class SignUp(generic.CreateView):
form_class = CustomUserCreationForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
pages/urls.py
from django.urls import path
from django.urls import path, include
from django.conf.urls import url
from django.conf import settings
from . import views
from .views import HomePageView, MyAccountView, AboutPageView, PrivacyPageView, ContactPageView
urlpatterns = [
path('about/', AboutPageView.as_view(), name='about'),
path('privacy/', PrivacyPageView.as_view(), name='privacy'),
path('contact/', ContactPageView.as_view(), name='contact'),
path('', HomePageView.as_view(), name='home'),
path('myaccount/', MyAccountView.as_view(), name='myaccount'),
url('avatar/', include('avatar.urls')),
url('myaccount/', include('avatar.urls')),
]
templates/myaccount/change.html
{% extends 'base.html' %}
{% load static %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
{% endblock content %}
Sir, I don't see any problem in the code shared. But here is 2 hints :
check if in base.html, you have included the block content.
add print(form) before the return in the view to see what you get (the print result will be on the terminal not the browser)
edit :
You should add an url that calls the view :
in url add this line :
path('testform/', view.get_name, name ='get_name')
and then on your brower, test the url 127.0.0.1:8080/testform/
you will need to include {% block content %} and {% endblock %} tags in your base.html file.
When you "extend" the base template, these tags indicate where the extra content should be inserted.
see tte Template Inheritance
#AlouaniYounes Looks like on base.html the my account page was being directed somewhere else. I made the fix to base.html and problem solved. Thanks!

Categories