This form was showing the radio-buttons when in the Function based views but changed to the checkbox when I introduced the Class based views, what could be the solution to this. I want them to be showing the radio-buttons again
forms.py
class ProductImagesForm(forms.ModelForm):
media = forms.ImageField(label='Image')
featured_image = forms.BooleanField(initial=True)
def __init__ (self, *args, **kwargs):
super(ProductImagesForm, self).__init__(*args, **kwargs)
self.fields['featured_image'] = forms.BooleanField( widget = forms.RadioSelect(attrs={'checked': 'true'}, choices=((self.prefix, 'featured'),)))
models.py
def product_download(instance, filename):
return '%s/%s' %(instance.product.slug, filename)
class ProductImages(models.Model):
product = models.ForeignKey(Product)
title = models.CharField(max_length=120)
media = models.ImageField(upload_to=product_download,
width_field='max_width',
height_field='max_height',
null=True, blank=True)
max_width = models.CharField(max_length=100, null=True, blank=True)
max_height = models.CharField(max_length=100, null=True, blank=True)
featured_image = models.BooleanField(default=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
def __unicode__(self):
return unicode(self.media)
class Meta:
verbose_name = "product image"
verbose_name_plural = "product images"
template
<form enctype="multipart/form-data" action="" method="post"> {% csrf_token %}
{{ form.as_p }}
{{ formset.management_form }}
<div class="link-formset">
{% for obj in formset %}
{{ obj.as_p }}
{% endfor %}
</div>
views.py
def get(self, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = ImagesFormset(queryset=ProductImages.objects.none())
return self.render_to_response(self.get_context_data(form=form, formset=formset))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = ImagesFormset(self.request.POST, self.request.FILES)
form_valid = form.is_valid()
formset_valid = formset.is_valid()
if form_valid and formset_valid:
seller = self.get_account()
form.instance.seller = seller
self.object = form.save()
media = formset.save(commit=False)
for img in media:
img.product = self.object
img.save()
formset.save()
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)`
After some good researching and keen documentation reading, I got to the point here, I hope it will help some other folks too
forms.py
class ProductImagesForm(forms.ModelForm):
media = forms.ImageField(label='Image')
featured_image = forms.BooleanField(initial=True)
def __init__ (self, *args, **kwargs):
super(ProductImagesForm, self).__init__(*args, **kwargs)
self.fields['featured_image'] = forms.BooleanField(queryset=ProductImages.objects.all(), widget = forms.RadioSelect( attrs={'checked': True}, choices=((self.prefix, 'featured_image')),))
def add_prefix(self, field):
if field == 'featured_image': return field
else: return self.prefix and ('%s-%s' % (self.prefix, field)) or field
class Meta:
model = ProductImages
fields = ['media', 'featured_image']
ImagesFormset = modelformset_factory(ProductImages, fields=('media', 'featured_image'), extra=5)
template
{{ formset.management_form }}
<div class="link-formset">
{% for choice in formset %}
<div>
{{ choice.media }}
<input type="radio" name="{{choice.featured_image.label}}">{{ choice.featured_image.label }}</
</div>
{% endfor %}
</div>
Related
I'm trying to do Django class-based CreateView inline formsets.
I have a product model and a productImage model and productImage is inline
everything looks fine but after i submit my product the images that are selected in formset will not save.
Here is my code
models.py:
class Product(models.Model):
name=models.CharField(max_length=200, db_index=True),
slug=models.SlugField(max_length=200, db_index=True, allow_unicode=True),
description=models.TextField(blank=True),
vector_column=SearchVectorField(null=True, editable=False),
meta={"indexes": (GinIndex(fields=["vector_column"]),)}
category = models.ForeignKey(Category, related_name='products',
on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
available = models.BooleanField(default=True)
def get_absolute_url(self):
return reverse('shop:product_detail', args=[self.id, self.slug])
class ProductImage(models.Model):
product = models.ForeignKey(Product, default=None, on_delete=models.CASCADE)
image = models.ImageField(upload_to='products/%y/%m/%d')
def __str__(self):
return self.product.name
views.py
class ProductCreate(StaffAccessMixin, ProductFormValidMixin, ProductFieldsMixin,
CreateView):
model = Product
def get_context_data(self, **kwargs):
context = super(ProductCreate, self).get_context_data(**kwargs)
if self.request.POST:
context['product_image_formset'] = ProductImageFormset(self.request.POST)
else:
context['product_image_formset'] = ProductImageFormset()
return context
template_name = 'products/product-create-update.html'
ProductFormValidMixin:
class ProductFormValidMixin():
def form_valid(self, form):
context = self.get_context_data()
product_image_formset = context['product_image_formset']
if self.request.user.is_superuser:
self.obj = form.save()
if product_image_formset.is_valid():
product_image_formset.instance = self.obj
product_image_formset.save()
return redirect('administration:product-update', pk=self.obj.pk)
else:
self.obj = form.save(commit=False)
self.obj.available = False
return super().form_valid(form)
ProductFieldsMixin:
class ProductFieldsMixin():
def dispatch(self, request, *args, **kwargs):
if request.user.is_superuser:
self.fields = ["name",
"slug",
"description",
"category",
"price",
"available",
"image",
]
else:
raise Http404
return super().dispatch(request, *args, **kwargs)
forms.py:
from django.forms.models import inlineformset_factory
from .models import Product, ProductImage
ProductImageFormset = inlineformset_factory(Product, ProductImage, fields=('image',),
extra=3)
Formset template:
<h5 class="text-info">Add Product Metas</h5>
{{ product_image_formset.management_form|crispy }}
{% for form in product_image_formset.forms %}
<tr class="{% cycle 'row1' 'row2' %} formset_row-{{product_image_formset.prefix }}">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field|as_crispy_field }}
</td>
{% endfor %}
</tr>
{% endfor %}
Everything is ok and my product form is saved after submit but the images i select in my form set won't save
Problem solved!
This code will work for other types of data but not for images and files
to solve this problem get_context_data function should be like this:
def get_context_data(self, **kwargs):
context = super(ProductCreate, self).get_context_data(**kwargs)
if self.request.POST:
context['product_image_formset']=ProductImageFormset(self.request.POST,
**self.request.FILES**)
else:
context['product_image_formset'] = ProductImageFormset()
return context
In my first code self.request.FILES was missing :) and because of that images couldn't saved
In my Blog Django Project I am trying to create a Like Feature, but currently facing the below error:
Reverse for 'post-detail' with no arguments not found. 1 pattern(s) tried: ['blog/(?P<slug>[-a-zA-Z0-9_]+)/$']
What should I do to prevent this error and to return back to the same Post-detail page after pressing the like button?
Here is the urls.py
path('blog/<slug:slug>/', PostDetailView.as_view(), name='post-detail'),
path('blogs/like', like_post, name='like-post'),
Here is the models.py
class Post(models.Model):
liked = models.ManyToManyField(User, default=None, blank=True, related_name='liked')
def num_likes(self):
return self.liked.all().count()
def get_absolute_url(self):
return reverse('blog:post-detail', kwargs={'slug': self.slug})
LIKE_CHOICES = (
('Like', 'Like'),
('Unlike', 'Unlike')
)
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
value = models.CharField(choices=LIKE_CHOICES, default='Like', max_length=10)
date_liked = models.DateTimeField(default=timezone.now)
def __str__(self):
return str(self.post)
Here is the views:
I have made a couple of trials but did not work, I have commented them in the end of the views.py as they returned like_post() missing 1 required positional argument: 'slug'
class PostListView(ListView):
model = Post
template_name = "blog/post_list.html" # <app>/<model>_<viewtype>.html
ordering = ['-date_posted']
context_object_name = 'posts'
paginate_by = 1
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html" # <app>/<model>_<viewtype>.html
def get(self, request, *args, **kwargs):
res = super().get(request, *args, **kwargs)
self.object.incrementViewCount()
return res
def like_post(request):
user=request.user
if request.method=='POST':
post_id=request.POST.get('post_id')
post_obj= Post.objects.get(id=post_id)
if user in post_obj.liked.all():
post_obj.liked.remove(user)
else:
post_obj.liked.add(user)
like,created=Like.objects.get_or_create(user=user,post_id=post_id)
if not created:
if like.value=='Like':
like.value='Unlike'
else:
like.value='Like'
like.save()
return redirect('blog:post-detail') <------------ Error Showing from Here
#post = get_object_or_404(Post, slug=Post.slug)
#if post.slug != slug:
#return redirect('blog:post-detail', slug=Post.slug)
Here is the template:
<form action="{% url 'blog:like-post' %}" method="POST">
{% csrf_token %}
<input type="hidden" name="post_id" value='{{post.id}}'>
{% if user not in obj.liked.all %}
<button class="ui button positive" type="submit"> Like</button>
{% else %}
<button class="ui button negative" type="submit">Unlike</button>
{% endif %}
<br>
<strong>{{ post.liked.all.count }} Likes</strong>
</form>
redirect('blog:post-detail', slug=post_obj.slug)
I want show all Articles from specific category in my template 'category_articles.list.html' at the link: path('category/<name_of_category_SLUG>/'
I have:
URLS
urlpatterns = [
path('show/<int:pk>/<slug:slug>', ArticleDetailView.as_view(), name='article_detail'),
path('all/', AllArticlesListView.as_view(), name='all_articles_list'),
path('category/<slug:slug>/', CategoryArticlesList.as_view(), name='category_articles_list'),
]
MODELS
class Created(models.Model):
created_on = models.DateTimeField(auto_now_add=True, null=True)
class Meta:
abstract = True
class ArticleCategory(Created):
category_name = models.CharField(max_length=128)
slug = models.SlugField(null=False, unique=False)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.category_name)
return super().save(*args, **kwargs)
def __str__(self):
return self.category_name
class Meta:
verbose_name_plural = 'Article categories'
class Article(Created):
title = models.CharField(max_length=120)
author = models.ForeignKey(User, on_delete=models.CASCADE)
snippet = models.TextField(null=False) # ustawić max_lenght
body = RichTextField(null=False)
category = models.ManyToManyField(ArticleCategory, related_name='articles') # TODO: ustawić on_delete
image = models.ImageField(blank=True, null=True, upload_to='article_image/')
slug = models.SlugField(null=False, unique=False)
def save(self, *args, **kwargs): # opisać tą funckję
if not self.slug:
self.slug = slugify(self.title)
return super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('news:article_detail', kwargs={'pk': self.pk, 'slug': self.slug})
def article_categories(self):
# zwraca nam wszystkie kategorię artykułu w stringu
return ", \n".join([x.category_name for x in self.category.all()])
def __str__(self):
return f"{self.title}"
class Meta:
verbose_name_plural = 'Articles'
VIEWS
class CategoryArticlesList(DetailView):
template_name = 'news/category_articles_list.html'
model = ArticleCategory
class AllArticlesListView(ListView):
template_name = 'news/articles_list.html'
model = Article
paginate_by = 2
def get_queryset(self):
return self.model.objects.all().order_by('-created_on')
class ArticleDetailView(DetailView):
template_name = 'news/article_detail.html'
model = Article
ARTICLES_LIST.HTML template
ALL NEWS
{% if object_list %}
{% for article in object_list %}
TITLE: {{ article.title }}
CATEGORY_iterator:
{% for category in article.category.iterator %}
{{ category | upper}}
{% endfor %}<br>
ARTICLE_DETAIL.HTML template
ONE SPECIFIC NEWS
<h3>TITLE: {{ article.title }}</h3>
<a href="{% url 'news:article_detail' pk=article.pk slug=article.slug %}">
{% url 'news:article_detail' pk=article.pk slug=article.slug %}</a> <br>
ID:
{{ article.id }} <br>
AUTHOR:
{{ article.author.username }} <br>
CATEGORY_iterator:
{% for category in article.category.iterator %}
{{ category | upper}}
{% endfor %}<br>
finally....
CATEGORY_ARTICLES_LIST.HTMLtemplate
ALL ARTICLES FROM CATEGORY: {{ articlecategory.category_name | upper }}
I don't know how put all articles to ARTICLE_DETAIL.HTML template...
You can iterate all articles from the reverse relation:
articlecategory.articles.all
I am not sure if you can use that in django templates and if it needs to be used with or without the "()".
I'm trying to create a form for edit table Person, but in this form field Car must be filtered by selected Company. I'm very new to python/django so this may be a bad approach, high level suggestions welcome. Here is what I have:
I have 3 models in models.py:
from django.db import models
class Company(models.Model):
company = models.CharField(max_length=25)
def __str__(self):
return self.company
class Car(models.Model):
car = models.CharField(max_length=25)
company = models.ForeignKey(Company, on_delete = models.CASCADE)
def __str__(self):
return self.car
class Person(models.Model):
name = models.CharField(max_length=20)
car = models.ForeignKey(Car, on_delete = models.CASCADE)
def __str__(self):
return self.name
views.py:
def index(request):
people = Person.objects.all().select_related('car__company')
table = PersonTable(people)
return render(request, 'index.html', {'table': table})
def edit(request, id):
person = Person.objects.get(id = id)
car = Car.objects.get(id = person.car_id)
company = Company.objects.get(id = car.company_id)
if request.method == 'POST':
form = PersonForm(car.company_id, request.POST, instance=person)
if form.is_valid():
form.save()
return redirect('/person/')
else:
companys = CompanyForm(instance=company)
form = PersonForm(car.company_id, instance=person)
return render(request, 'person/edit.html', {'companys':companys, 'form':form})
def Company_select(request):
if request.method == 'POST':
form = CompanySelectForm(request.POST)
if form.is_valid:
return redirect('/person/edit/1/') # Return to form PersonForm with id=1.
# How to return selected Company?
# How to return to page with other id?
else:
form = CompanySelectForm()
return render(request, 'person/company_select.html', {'form':form})
urls.py:
app_name = 'person'
urlpatterns = [
path('edit/<int:id>/', views.edit),
path('company/', views.Company_select, name='company'),
path('', views.index),
]
forms.py
class CompanyForm(forms.ModelForm):
class Meta:
model = Company
fields = ('company',)
class CompanySelectForm(forms.Form):
company = forms.ModelChoiceField(queryset=Company.objects.all().order_by('company'))
class Meta:
fields = ('company',)
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ('name', 'car',)
def __init__(self, company, *args, **kwargs):
super(PersonForm, self).__init__(*args, **kwargs)
self.fields['car'].queryset = Car.objects.filter(company=company) #Filter by Company
edit.html
{% extends "base.html" %}
{% block header %}Edit{% endblock header %}
{% block content%}
<form action="" method="post">
{% csrf_token %}
{{ form.as_table }}
{{ companys.as_table }}
<button type="button" class="btn btn-default btn-xs">Select Company</button>
<button class="btn btn-primary" type="submit" name="submit">submit</button>
</form>
{% endblock content %}
company_select.html
{% extends "base.html" %}
{% block header %}Select Company{% endblock header %}
{% block content%}
<form action="" method="post">
{% csrf_token %}
{{ form.as_table }}
<button class="btn btn-primary" type="submit" name="submit">submit</button>
</form>
{% endblock content %}
This is my proposition : tested
form.py => add attribut that will call a JS function defined in your .html
class PersonForm(ModelForm):
class Meta:
model = Person
fields = ('name', 'company', 'car')
def __init__(self, *args, **kwargs,):
super(PersonForm, self).__init__(*args, **kwargs)
class SelectForm(Form):
name = CharField(label='name')
company = ChoiceField(label='company')
car = ChoiceField(label='car')
class Meta:
fields = ('name', 'company', 'car', )
def __init__(self, cars=None, *args, **kwargs, ):
super(SelectForm, self).__init__(*args, **kwargs)
self.fields['car'].choices = cars
self.fields['company'].choices = tuple(Company.objects.all().values_list(flat=False))
self.fields['company'].widget.attrs['onchange'] = "load_car()"
edit.html: => define the JS function
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form id='myform' method="POST">
{% csrf_token %}
{{ form }}
</form>
<button class="btn btn-primary" type="submit" name="submit">submit</button>
</body>
<script>
function load_car()
{
document.getElementById('myform').action = "/stack/myview";
document.getElementById("myform").submit();
}
</script>
</html>
Views.py:=> new view that will look for the company name in the database and return to the template
def index(request): # RF configuration FORM
form = PersonForm()
content = {'form': form}
return render(request, 'stack/index.html', content)
def edit(request, my_id):
if request.method == 'POST':
form = PersonForm(request.POST)
if form.is_valid():
form.save()
# Do ....
return HttpResponseRedirect(reverse('stack:index'))
else:
person = Person.objects.values_list().get(id__exact=my_id)
cars = []
[cars.append((x['car'], x['car'])) for x in
list(Car.objects.filter(company__exact=person[2]).values('car'))]
form = SelectForm(cars=cars, initial={'name': person[1], 'company': person[2], 'car': person[3]})
content = {'form': form}
return render(request, 'stack/edit.html', content)
else:
person = Person.objects.values_list().get(id__exact=my_id)
cars = []
[cars.append((x['car'], x['car'])) for x in list(Car.objects.filter(company__exact=person[2]).values('car'))]
form = SelectForm(cars=cars, initial={'name': person[1], 'company': person[2], 'car': person[3]})
content = {'form': form}
return render(request, 'stack/edit.html', content)
def myview(request):
cars = []
[cars.append((x['car'], x['car'])) for x in list(Car.objects.filter(company__exact=request.POST['company']).
values('car'))]
form = SelectForm(cars=cars, initial={'name': request.POST['name'], 'company': request.POST['company'], 'car': None})
content = {'form': form}
return render(request, 'stack/edit.html', content)
I install django-smart-selects
In models.py:
from smart_selects.db_fields import ChainedForeignKey
...
class Person(models.Model):
name = models.CharField(max_length=20)
company = models.ForeignKey(Company, on_delete = models.CASCADE)
car = ChainedForeignKey(
Car,
chained_field="company",
chained_model_field="company",
show_all=False,
auto_choose=True,
sort=True,
on_delete=models.CASCADE,
blank=True,
null=True)
def __str__(self):
return self.name
I have a problem when saving/creating new post for blog.
I already have Post model and each post has it's own category. so I have Category model too. In CreateView template I already got all categories from DB and displayed in select/option tag. The issue is I want to save category_id when I create new post. But I don't know how? How can I say if form POSTED get category_id and save it for Post model that has category_id field?
View:
class PostCreateForm(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'short_text', 'long_text', 'post_pic']
def get_context_data(self, **kwargs):
context = super(PostCreateForm, self).get_context_data(**kwargs)
context['categories'] = Category.objects.all().order_by('id')
return context
def form_valid(self, form, **kwargs):
form.instance.author = self.request.user
return super().form_valid(form)
Model:
class Category(models.Model):
title = models.CharField(max_length=255)
def __str__(self):
return self.title
class Post(models.Model):
title = models.CharField(max_length=255)
short_text = models.TextField()
long_text = models.TextField()
post_pic = models.ImageField(default="post_pic.jpg",
blank=False, upload_to='post_pics')
date_published = models.DateTimeField(default=timezone.now())
date_upadated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, default=None,
on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})`
post_form.html
{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title_head %}
New Post
{% endblock title_head %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4 pb-2">New Post</legend>
{{ form|crispy }}
<select class="form-control" name="category">
{% for category in categories %}
<option value="{{ category.id }}">{{ category }}</option>
{% endfor %}
</select>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-outline-info">Post</button>
</div>
</form>
{% endblock content %}
There are a few ways to achieve this. Here is the simplest:
If you expect the Catagories to remain the same then you can hard code them and use a CharField with the choices= keyword on your Post model.
class Post(models.Model):
CATEGORY_CHOICES = (
('Horror', 'Horror'),
('Romance', 'Romance'),
('Comedy', 'Comedy'),
)
category = models.CharField(choices=CATEGORY_CHOICES)
Then all you need to do is add 'category' to your fields list in the CreateView.
Side Note You should probs rename your CreateView it's a view not a form. This may cause confusion.
Solved!
we have access category in list of fields so it's useless to fetch all categories then displaying them in select-option tag in html page. just add category field in list of fields
Change PostCreateForm
change it from this
class PostCreateForm(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'short_text', 'long_text', 'post_pic']
def get_context_data(self, **kwargs):
context = super(PostCreateForm, self).get_context_data(**kwargs)
context['categories'] = Category.objects.all().order_by('id')
return context
def form_valid(self, form, **kwargs):
form.instance.author = self.request.user
return super().form_valid(form)
to this
class PostCreateForm(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'short_text', 'long_text', 'post_pic', 'category']
def get_context_data(self, **kwargs):
context = super(PostCreateForm, self).get_context_data(**kwargs)
return context
def form_valid(self, form, **kwargs):
form.instance.author = self.request.user
return super().form_valid(form)