I'm trying to upload file in a specific folder and store its path in DB through forms(CBV), but it doesn't work!!
this is my code (forms, models, views, template).
I'm selecting the file through forms then I submit the form, but it doesn't submit.
#views.py
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
# fields = ['title', 'content']
success_url = reverse_lazy('blog/new_post.html')
template_name = 'blog/new_post.html'
form_class = PostCreateForm
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect(self.success_url)
else:
return render(request, self.template_name, {'form': form})
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
#model.py
class Post(models.Model):
title = models.CharField(max_length=1000)
content = models.TextField()
xml_file = models.FileField(null=True, upload_to='xml_files')
rate = models.FloatField(null=True, blank=True, default=None)
post_date = models.DateTimeField(default=timezone.now)
post_update = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
# return '/detail/{}'.format(self.pk)
return reverse('detail', args=[self.pk])
class Meta:
ordering = ('-post_date', )
#forms.py
class PostCreateForm(forms.ModelForm):
title = forms.CharField( max_length=1000)
content = forms.CharField(widget=forms.Textarea(
attrs={'rows': 15, 'placeholder': 'write here'}
), max_length=50000)
xml_file = forms.FileField(label='upload file')
class Meta:
model = Post
fields = ['title', 'content', 'xml_file', ]
#new_post.html
{% block content %}
{% load crispy_forms_tags %}
<div class="border p-4 mb-5">
<legend class="border-bottom pb-1 mb-3">new post</legend>
<form method="POST">
{% csrf_token %}
{{form|crispy}}
<input class="btn btn-secondary mt-4" type="submit" value="post">
</form>
</div>
{% endblock content %}
When you are uploading media like what you've got, it is necessary to add the following part to your <form>:
enctype=multipart/form-data
Related
Is there a way for the button to do the following? : When user press the button it takes the user.username of the current user and automatically fill up a form of BookInstance from models.py and save it to the database.
From models.py :
class BookInstance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
book = models.ForeignKey("Book", on_delete=models.RESTRICT, null=True)
imprint = models.CharField(max_length=200, blank=True, null=True)
due_back = models.DateField(blank=True, null=True)
borrower = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True)
LOAN_STATUS = (
('m', 'Maintenance'),
('o', 'On Loan'),
('a', 'Available'),
('r', 'Reserved')
)
status = models.CharField(
max_length=1, choices=LOAN_STATUS, blank=True, default='a')
class Meta:
ordering = ['due_back']
def __str__(self):
return f'{self.id} - {self.book.title}'
def get_absolute_url(self):
return reverse("catalog:book_list")
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.ForeignKey(
'Author', on_delete=models.SET_NULL, null=True)
summary = models.TextField(
max_length=500, help_text="Enter brief description")
isbn = models.CharField('ISBN', max_length=13, unique=True)
genre = models.ManyToManyField(Genre, help_text="Select genre")
language = models.ForeignKey(
"Language", on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("catalog:book_detail", kwargs={"pk": self.pk})
This is my from my views.py :
def borrowBook(request, pk):
context = {
'book_instance': BookInstance.objects.all()
}
success_url = reverse_lazy('catalog:index')
if request.method == "POST":
form = BorrowForm(request.POST or None)
if form.is_valid():
book_instance.id = BookInstance.objects.get(pk=pk)
book_instance.book = BookInstance.objects.get(book=book)
book_instance.borrower = request.user
book_instance.status = 'o'
book_borrowed_count = BookInstance.objects.filter(
owner=request.user).count()
if book_borrowed_count < 4:
book_instance = form.save(commit=False)
book_instance.save()
else:
print("Maximum limit reached!")
return redirect('catalog:index')
return render(request, 'catalog/book_detail.html', {'form': form})
here's from my BorrowForm from forms.py :
class BorrowForm(forms.ModelForm):
class Meta:
model = BookInstance
fields = '__all__'
here's my from my urls.py :
path("book_list/book/<int:pk>/borrow", views.borrowBook, name="borrowBook"),
I also tried using a CBV here:
class BorrowBookView(PermissionRequiredMixin, CreateView):
permission_required = 'login'
model = BookInstance
fields = '__all__'
template_name = 'catalog/borrow_form.html'
success_url = reverse_lazy('catalog:index')
def post(self, request, *args, **kwargs):
book_instance.id = BookInstance.objects.get(pk=pk)
book_instance.book = BookInstance.objects.get(book=book)
book_instance.borrower = request.user
book_instance.status = 'o'
book_instance = form.save(commit=False)
book_instance.save()
CBV path from urls.py :
path("book_list/book/<int:pk>/borrow/",
views.BorrowBookView.as_view(), name="book_borrow"),
Here's how I implemented the button using suggestions from here:
<form action="#" method="post">
{% csrf_token %}
<button
type="submit"
class="btn btn-dark flex-shrink-0 "
value="{{ book.id }}">Borrow
</button>
but when I pressed it doesn't seem to save anything to the database and just popup errors, though I may implemented the button or the function from my is views wrong. Thanks and appreciate for any help provided.
You do not need a Django form for this. Forms are usually used for when you want to create objects or edit its fields (like in the admin page). While here an user is not editing nor creating an object (book), but borrowing one.
So basically, we just need to list all available book instances (status='a'), and have a button to "borrow" it. The borrow action is to update status to 'r' or 'o' and have the borrower updated to the current user which is guaranteed to exist inside the request object by LoginRequiredMixin
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import View
from django.contrib import messages
from django.urls import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from .models import BookInstance
class BorrowBook(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
book_id = kwargs['pk']
available_books = BookInstance.objects.filter(book__pk=book_id, status='a')
return render(request, 'borrow_book.html', {'available_books': available_books})
def post(self, request, *args , **kwargs):
book_instance_id = request.POST['id']
obj = get_object_or_404(BookInstance, id=book_instance_id)
obj.status = 'r'
obj.borrower = request.user
# Maybe also update due_back data
# obj.due_back = ...
obj.save()
messages.success(request, "Your book is reserved.")
# I used the redirection to the same template
# But you probably want to send the user somewhere else
return HttpResponseRedirect(reverse('core:borrow-book', kwargs={'pk': 1}))
borrow_book.html
{% extends 'base.html' %}
{% block content %}
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% for instance in available_books %}
<form action="{% url 'core:borrow-book' instance.book.id %}" method="POST">
{% csrf_token %}
<input type="hidden" name="id" value="{{instance.id}}">
<p>{{instance.book}}</p>
<p>{{instance.book.language.name}}</p>
<input type="submit" value="Borrow this book.">
</form>
{% endfor %}
{% endblock content %}
urls.py
from django.urls import path
from core import views
app_name = 'core'
urlpatterns = [
path("book_list/book/<int:pk>/borrow/", views.BorrowBook.as_view(), name="borrow-book"),
]
I have two models with foreign key relation
models.py
from django.db import models
# Create your models here.
class Project(models.Model):
STATUSES = (
('Ongoing', 'Ongoing'),
('Completed', 'Completed')
)
YEARS = (
(2019, 2019),
(2020, 2020),
(2021, 2021),
(2022, 2022)
)
name = models.CharField(max_length=200)
client = models.CharField(max_length=100)
year = models.SmallIntegerField(choices=YEARS)
status = models.CharField(max_length=10, choices=STATUSES)
picture = models.ImageField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
class Photo(models.Model):
project = models.ForeignKey("Project", on_delete=models.CASCADE, related_name="images", blank=True, null=True)
image = models.ImageField()
description = models.CharField(max_length=100, blank=True, null=True)
slide = models.BooleanField(default=False)
I want photos and project to be created on the same form so I've used inline_formset_factory
forms.py
from django.forms import inlineformset_factory
from projects.models import Photo, Project
from django.forms import ModelForm
class ProjectModelForm(ModelForm):
class Meta:
model = Project
fields = (
'name',
'client',
'year',
'picture',
'status',
'description',
)
class PhotoModelForm(ModelForm):
class Meta:
model = Photo
fields = (
'image',
'slide',
'description'
)
PhotoFormset = inlineformset_factory(Project, Photo, form=PhotoModelForm, extra=1)
I used the generic CreateView
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from projects.forms import PhotoFormset, ProjectModelForm
from django.shortcuts import redirect, reverse, render
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from .models import Photo, Project
# Create your views here.
class ProjectCreateView(LoginRequiredMixin, CreateView):
template_name = 'projects/project_create.html'
form_class = ProjectModelForm
def get_context_data(self, **kwargs):
context = super(ProjectCreateView, self).get_context_data(**kwargs)
context['photos_formset'] = PhotoFormset()
return context
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
photo_formset = PhotoFormset(self.request.POST)
if form.is_valid() and photo_formset.is_valid():
return self.form_valid(form, photo_formset)
else:
return self.form_invalid(form, photo_formset)
def form_valid(self, form, photo_formset):
self.object = form.save(commit=False)
self.object.save()
print('valid')
photos = photo_formset.save(commit=False)
for photo in photos:
photo.project = self.object
photo.save()
return reverse('projects:projectspage')
def form_invalid(self, form, photo_formset):
if not photo_formset.is_valid():
print('invalid formset')
return self.render_to_response(
self.get_context_data(form=form, photos_formset=photo_formset)
)
def get_success_url(self):
return reverse('projects:projectspage')
this is the template
{% extends 'base.html' %}
{% load crispy_forms_filters %}
<!-- crispy_forms_tags -->
{% block content %}
<div class="container">
<h2>create new project</h2>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<h2 class="display-6 my-5">
Add photos
</h2>
{{ photos_formset.as_p }}
<input type="submit" value="Create" class="btn btn-primary">
</form>
</div>
{% endblock content %}
When I submit form_invalid is returned from the post method of the view
How do i get the inline_formset to validate or is there better way of doing it
I prefer to write those views as functions, seems more straightforward, try:
#login_required
def post_news(request):
if request.method == 'POST':
project_form = ProjectModelForm(request.POST, request.FILES)
photo_form = PhotoModelForm(request.POST, request.FILES)
picture = request.FILES['picture']
image = request.FILES['image']
if project_form.is_valid() and photo_form.is_valid():
project_instance = project_form.save(commit=False)
project_instance.picture = picture
project_instance.save()
photo_instance = project_form.save(commit=False)
photo_instance.project = project_instance
photo_instance.image = image
photo_instance.save()
return reverse('projects:projectspage')
else:
project_form = ProjectModelForm()
photo_form = PhotoModelForm()
return render(request, 'projects/project_create.html',
{'project_form': project_form, 'photo_form': photo_form})
You will have to account for a case when no picture for the project model is is provided I think. Just pop both forms into your template and this should work.
I'm building an app where I can add recipes and add ingredients to those recipes. On view recipe_details I have a button to add_new_ingredient. When I'm on new_ingredient_form I want to have back button to get back to the details of recipe. I'm trying to pass recipe's pk but it doesn't work. How am I able to pass recipe's pk to be able to back to previous view?
models.py
class Recipe(Timestamp):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
title = models.CharField(max_length=100, unique=True)
preparation = models.TextField()
def __str__(self):
return self.title
class Ingredient(Timestamp):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
amount = models.PositiveSmallIntegerField(blank=True, null=True)
unit = models.ForeignKey('Unit', on_delete=models.SET_NULL, blank=True, null=True)
def __str__(self):
return self.name
views.py
class RecipeView(generic.DetailView):
model = Recipe
context_object_name = 'recipe'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['ingredients_list'] = Ingredient.objects.filter(recipe=self.object.pk)
return context
class AddIngredientView(generic.edit.CreateView):
model = Ingredient
fields = [
'name',
'amount',
'unit'
]
success_url = '/'
template_name = 'recipes/add_ingredient.html'
def dispatch(self, request, *args, **kwargs):
self.recipe = get_object_or_404(Recipe, pk=self.kwargs['pk'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.recipe = self.recipe
return super().form_valid(form)
def get_success_url(self):
if 'add_another' in self.request.POST:
url = reverse_lazy('recipes:add_ingredient', kwargs={'pk': self.object.recipe_id})
else:
url = reverse_lazy('recipes:recipe', kwargs={'pk': self.object.recipe_id})
return url
add_ingredient.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-success" type="submit">Save</button>
<button class="btn btn-success" type="submit" name="add_another">Save and add another</button>
Back
</form>
{% endblock %}
You can go to the previous page by adding {{request.META.HTTP_REFERER}} to the href property of the button.
Django request to find previous referrer
I am new in Django and Stackoverflow, so please accept my apology if my codes is not standard.
I try to create a blogging website. Users can create and update posts and each post can have one or more categories or no category. I use form for Post and Formset for Category. However, in Updateview for some reason I couldnt save the formset!!!!
models.py
class Post(models.Model):
title = models.CharField(max_length=128)
text = models.TextField(blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
published_date = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
description = models.TextField(blank=True)
posts = models.ManyToManyField(Post,
blank=True,related_name='categories')
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
forms.py
from django import forms
from blogging.models import Post, Category
class PostUpdateForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'text', 'published_date']
CATEGORY_CHOICES = [('', 'Choose from the list')]
for c in Category.objects.all():
CATEGORY_CHOICES.append((c, c))
class CategoryUpdateForm(forms.ModelForm):
class Meta:
model = Category
fields = ['name']
labels = {'name': 'Category'}
help_texts = {'name': 'Choose category for your post'}
widgets = {
'name': forms.Select(choices=CATEGORY_CHOICES)
}
CategoryFormset = forms.modelformset_factory(Category,
form=CategoryUpdateForm, extra=1,
max_num=3, can_delete=True)
views.py
from blogging.models import Post, Category
from blogging.forms import PostUpdateForm, CategoryFormset
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
template_name = 'blogging/post_update.html'
form_class = PostUpdateForm
formset_class = CategoryFormset
def form_valid(self, form):
form.instance.author = self.request.user
context = self.get_context_data()
formset = context['formset']
if formset.is_valid():
category_form = formset.save(commit=False)
category_form.posts.add(self.get_object())
category_form.save()
formset.save()
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
post = self.get_object()
if self.request.POST:
context['formset'] = self.formset_class(self.request.POST)
else:
context['formset'] =
self.formset_class(queryset=post.categories.all())
return context
template
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Profile Info</legend>
{{ u_form | crispy }}
{{ p_form | crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info"
type="submit">Update</button>
</div>
</form>
I could find the answer. By using extra-view package and use the CreateInlineView, and UpdateInlineView all can be solved. https://django-extra-views.readthedocs.io/en/latest/
I want add comment to post on his page ("post detail" page).
I was find answer, but it create comment on other page. I want create comment on page of "post detail".
urls.py
url(r'^post/(?P<pk>\d+)/create/$', views.CommentCreate.as_view(), name='comment_create'),
models.py
class Comment(models.Model):
description = RichTextUploadingField()
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
comment_date = models.DateTimeField(auto_now_add=True, null=True)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
class Meta:
ordering = ["-comment_date"]
def __str__(self):
return "{}".format(self.description)
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['description']
views.py
class PostDetailView(generic.DetailView):
model = Post
class CommentCreate(LoginRequiredMixin, CreateView):
model = Comment
fields = ['description']
def get_context_data(self, **kwargs):
context = super(CommentCreate, self).get_context_data(**kwargs)
context['post'] = get_object_or_404(Post, pk = self.kwargs['pk'])
return context
def form_valid(self, form):
form.instance.author = self.request.user
form.instance.post=get_object_or_404(Post, pk = self.kwargs['pk'])
return super(CommentCreate, self).form_valid(form)
def get_success_url(self):
return reverse('post-detail', kwargs={'pk': self.kwargs['pk'],})
comment_form.html
...
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<button type="submit">Submit</button>
</form>
...
post_detail.html
...
{% for comment in post.comment_set.all %}
<p>{{ comment.author }} ({{ comment.comment_date }}) {{ comment.description|safe }}</p>
{% endfor %}
<hr>
{% if user.is_authenticated %}
<p>Add a new comment</p>
...
I think, "comment_form" need to redirect to "post_detail", not generate new page for comment form.
And сould you tell, which parameters has a RichTextField (CKE), how change width, height field only in comment?
If you want the comment form right in your detail page then all you have to do is to add the form and post function in your View,
class PostDetailView(DetailView):
model = Post
template_name = 'yourdetailpage.html'
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['commentform'] = CommentForm()
return context
def post(self, request, pk):
post = get_object_or_404(Post, pk=pk)
form = CommentForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.post = post
obj.author = self.request.user
obj.save()
return redirect('detail', post.pk)
And now you can add your form in your html. And add a submit button to post comment.