I added an additional Model to the polls application (see django tutorial) which is intended to be a parent for a set of questions:
models.py
class Section(models.Model):
section_text = models.CharField(max_length=255)
section_description = models.TextField(blank=False)
slug = models.SlugField(unique=True, null=True)
def __unicode__(self):
return self.section_text
def save(self, *args, **kwargs):
self.slug = slugify(self.section_text)
super(Section, self).save(*args, **kwargs)
class Question(models.Model):
section = models.ForeignKey(Section)
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __unicode__(self):
return self.question_text
This works fine in the admin. Each question is linked to one section.
Displaying the sections is no problem either:
views.py
class UmfrageView(ListView):
model = Section
context_object_name = 'latest_section_list'
template_name = 'umfrage.html'
But if I want to pass a section via slug to DetailView it doesn't work (if I use generic.ListView instead, it displays questions from all sections):
urls.py
url(
regex=r'^(?P<slug>[-\w]+)/$',
view=DetailView.as_view(),
name='detail'
),
views.py
class DetailView(ListView):
model = Question
context_object_name = 'latest_question_list'
template_name = 'detail.html'
detail.html
{% if latest_question_list%}
{% for question in latest_question_list %}
<p>{{ question.question_text }}</p>
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
{% endfor %}
{% endif %}
If I use generic.DetailView
class DetailView(DetailView):
[...]
the following error shows up:
"Cannot resolve keyword u'slug' into field. Choices are: choice, id, pub_date, question_text, section"
How do I get the set of questions from one particular section and still have an human-friendly URL via slug?
Thanks!
(If further code is required, I'm more than happy to update)
In your DetailView add method get_queryset() to return only required objects as below
class DetailView(ListView):
model = Question
context_object_name = 'latest_question_list'
template_name = 'detail.html'
def get_queryset(self, **kwargs):
slug = self.kwargs.get('slug') or kwargs.get('slug')
if slug:
return Question.objects.filter(section__slug=slug)
else:
return Question.objects.all()
Related
I am making a django project and I have a form for the User to add a Vehicle Manually that will be assigned to him. I also would like to had an option for the user to choose a vehicle based on the entries already present in the database.
vehicles/models.py
class Vehicle(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
nickname = models.CharField(unique = True, max_length=150)
date_joined = models.DateTimeField(default=timezone.now)
brand = models.CharField(max_length=150)
battery = models.CharField(max_length=150)
model = models.CharField(max_length=150)
def __str__(self):
return self.nickname
def get_absolute_url(self):
return reverse('vehicle-list')
class Meta:
db_table = "vehicles"
I created a form so the user can add his Vehicles as such:
vehicles/forms.py
class VehicleAddFormManual(forms.ModelForm):
class Meta:
model = Vehicle
fields = ('brand','model', 'battery', 'nickname')
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
self.fields['brand']
self.fields['model']
self.fields['battery']
self.fields['nickname']
The corresponding view:
vehicles/views.py
class AddVehicleViewManual(LoginRequiredMixin, CreateView):
model = Vehicle
form_class = VehicleAddFormManual
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
The html file:
vehicles/templates/vehicles/vehicle_form.html
{% extends "blog/base.html" %}
{% block content %}
{% load crispy_forms_tags %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">New Vehicle</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Submit</button>
</div>
</form>
</div>
{% endblock content %}
I would like to add another form in which the user has a dropdown with option with the brands, models and batteries that already exist in the database. If there's a car in the database with brand: Tesla, model: Model 3, battery: 50 kWh, then it would appear in the dropbox as a choice for each field.
I'm not sure how to do this and sorry for the newbie question... Thanks in advance!
I once had to do something similar, but I needed a form which had one checkbox for each item in a list of externally-supplied strings. I don't know if this is the cleanest way, but I used python metaclasses:
class SockSelectForm(forms.Form):
#staticmethod
def build(sock_names):
fields = {'sock_%s' % urllib.parse.quote(name):
forms.BooleanField(label=name, required=False)
for name in sock_names}
sub_class = type('DynamicSockSelectForm', (SockSelectForm,), fields)
return sub_class()
In my get() method, I instantiate it as:
form = SockSelectForm.build(names)
and the corresponding form handling in the post() method is:
form = SockSelectForm(request.POST)
I suspect if you look under the covers of Django's ModelForm, you'd see something similar, but I couldn't use ModelForm because it's too closely tied to the model system for what I needed to do.
model.py
class DropdownModel(models.Model):
brand = models.CharField(max_length=150)
battery = models.CharField(max_length=150)
model = models.CharField(max_length=150)
def __str__(self):
return self.brand.
form.py
from .models import DropdownModel
all_brand = DropdownModel.objects.values_list('brand','brand')
all_battery = DropdownModel.objects.values_list('battery','battery')
all_model= DropdownModel.objects.values_list('model','model')
class DropdownForm(forms.ModelForm):
class Meta:
model = DropdownModel
fields = "__all__"
widgets = {
'brand':forms.Select(choices=all_brand),
'battery':forms.Select(choices=all_battery),
'model':forms.Select(choices=all_model),
}
view.py
from django.shortcuts import render
from .form import DropdownForm
# Create your views here.
def HomeView(request):
form = DropdownForm()
context = {'form':form}
return render(request,'index.html',context)
index.html
{% extends "base.html" %}
{% load static %}
{% block title %}
Index | Page
{% endblock title %}
{% block body %}
{{form.as_p}}
{% endblock body %}
Output-
Note- if u can't see updated values in dropdown do server restart because localhost not suport auto update value fill in dropdown it's supoorted on live server
Thank you
Following along the Django polls app tutorial, I was wondering if instead of having a Charfield for the choice Model and manually adding every response/choice to the database; Is it possible to have choices?
For example:
class Poll(models.Model):
text = models.CharField(max_length=255)
pub_date = models.DateField()
def __str__(self):
return self.text
class Choice(models.Model):
question = models.ForeignKey(Poll, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=255)
votes = models.IntegerField(default=0)
def __str__(self):
return "{} - {}".format(self.question.text[:25],
self.choice_text[:25])
You have standard choices for every Poll like this:
class Poll(models.Model):
text = models.CharField(max_length=255)
pub_date = models.DateField()
def __str__(self):
return self.text
class Choice(models.Model):
VOTING_CHOICES = (
('Aye', 'Aye'),
('Nay', 'Nay'),
('Abstain', 'Abstain'),
)
question = models.ForeignKey(Poll, on_delete=models.CASCADE)
choice_text = models.CharField(
max_length=7,
choices=VOTING_CHOICES,
default='Aye',
)**
votes = models.IntegerField(default=0)
def __str__(self):
return "{} - {}".format(self.question.text[:25],
self.choice_text[:25])
Poll Detail page
========
{{ poll }}
<form action="" method="post">
{% csrf_token %}
{% for choice in poll.choice_set.all %}
{% for i,k in choice.VOTING_CHOICES %}
<input type="radio"
name="option"
id="choice{{forloop.counter}}"
value="{{ i }}"/>
<label for="choice{{forloop.counter}}">{{ k }}</label>
{% endfor %}
{% endfor %}
<input type="submit" value="Vote">
</form>
views.py
def poll_detail(request, poll_id):
#render poll detail page
poll= get_object_or_404(Poll, id=poll_id)
if request.method =="POST":
print(request.POST)
# Debug to see what data I am posting on form submission
context = {
'poll':poll,
}
return render(request, 'app/poll_detail.html', context)
Does that make sense? Every time I try to implement this, I either get an empty dictionary response when I POST from the form or the options show up in tuples(or do not render at all).
lets's say that i have three categories (tutorials, news, jobs).
and i have class based views to list all posts, list posts by category and create new posts.
and sure post is the same model and fields to all categories.
my problem is :
if user was in category list template (let's say tutorial) .. i want the user when he create new post .. it is saved directly to tutorial category .. and if user was in list template (let's say news) .. he will create new post which will be saved directly to news category.
i mean create new post saved directly to current category.
i believe i will use (pass url parameter to class based views) but actually i failed to do that .. and i searched tonnage of questions without got what i want.
can any body help .. with sample please.
models.py
class Category(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50, unique=True)
def save(self, *args, **kwargs):
if not self.slug and self.name:
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
views.py
def PostListView(request, category_slug=None):
category = None
posts = Post.objects.all().prefetch_related().annotate(commentscountperpost=Count('comments'))
categories = Category.objects.prefetch_related().annotate(total_product_category=Count('post'))
if category_slug:
category = Category.objects.get(slug=category_slug)
posts = posts.filter(category=category)
context = {
'title': 'Home Page',
'posts': posts,
'total_posts': total_posts,
'categories': categories,
'category': category,}
return render(request, 'blog/index.html', context)
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
template_name = 'blog/new_post.html'
form_class = PostCreateForm
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
forms.py
class PostCreateForm(forms.ModelForm):
title = forms.CharField(label='Title')
content = forms.CharField(label='Content', widget=forms.Textarea)
class Meta:
model = Post
fields = ['title', 'content']
urls.py
path('index_list/', PostListView, name='list'),
path('<slug:category_slug>', PostListView, name='post_category_list'),
path('new_post/', PostCreateView.as_view(), name='new_post'),
new_post.html template
{% extends 'base.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="Add New Post">
</form>
</div>
{% endblock content %}
list.html template
{% for category in categories %}
<h5><a class="text-primary" href="{% url 'post_category_list' category.slug %}">
{{ category.name }} ({{ category.total_product_category }})</a></h5>
{% endfor %}
<h5>{{ total_posts }} Total Posts </h5>
{% if category %}
New {{ category }}
{% endif %}
You can try like this:
class PostCreateView(generic.CreateView):
model = Post
template_name = 'blog/new_post.html'
form_class = CreatePostForm
slug_url_kwarg = 'slug'
def form_valid(self, form):
category = Category.objects.get(slug=self.kwargs['slug'])
form.instance.category = category
form.instance.author = self.request.user
return super(PostCreateView, self).form_valid(form)
And in the urls
path('new_post/<slug>/', PostCreateView.as_view(), name='new_post'),
yes i found it , depending on arjun answer, greate thanks for arjun
in list posts per category template change :
New {{ category }}
to:
New {{ category }}
and it works fine,
thanks.
EDITS AVAILABLE BELOW!
My goal:
Category1
----Option1
----Option2
--Option3
Category2
----Option1
----Option2
etc.
I have a parent model (Venue) and a child model (Amenity). A venue can have many amenities.
while configuring my initial data and presenting it with {{form.as_p}} everything works as expected.
But when I try to render my own custom form, so that I can apply a loop, It doesn't pre-populate them.
Here is my template:
<form method="POST" class="ui form">
{% csrf_token %}
{% for category in categories %}
<h4 class="ui horizontal divider header">
<i class="list icon"></i>
{{category.category}}
</h4>
<p class="ui center aligned text"><u>{{category.description}}</u></p>
{% for amenity in category.amenity_set.all %}
<div class="inline field">
<label for="choices_{{amenity.id}}"></label>
<div class="ui checkbox">
<input id="choices_{{amenity.id}}" type="checkbox" value="{{amenity.id}}" name="choices">
<label><span data-tooltip="{{amenity.description}}" data-position="top left">{{amenity}}</span></label>
</div>
</div>
{% endfor %}
{% endfor %}
<button type="submit" name="submit" class="ui button primary">Next</button>
</form>
my ModelForm:
class AmenitiesForm(ModelForm):
class Meta:
model = Venue
fields = ('choices',)
choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=forms.CheckboxSelectMultiple,)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if kwargs.get('instance'):
initial = kwargs.setdefault('initial', {})
initial['choices'] = [c.pk for c in kwargs['instance'].amenity_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self)
instance.amenity_set.clear()
instance.amenity_set.add(*self.cleaned_data['choices'])
return instance
and my views.py:
class AddAmenitiesView(LoginRequiredMixin, CreateView):
"""
AddAmenitiesView is the view that prompts the user to select the amenities of their venue.
"""
model = Venue
form_class = AmenitiesForm
template_name = 'venues/add_amenities.html'
def parent_venue(self):
"""
returns the parent_venue based on the kwargs
:return:
"""
parent_venue = Venue.objects.get(id=self.kwargs["venue_id"])
return parent_venue
def get_initial(self):
initial = super().get_initial()
initial['choices'] = self.parent_venue().amenity_set.all()
return initial
def form_valid(self, form):
venue = Venue.objects.get(id=self.kwargs['venue_id'])
form.instance = venue
# form.instance.owner = self.request.user
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["parent_venue"] = self.parent_venue()
context["categories"] = AmenitiesCategory.objects.all()
return context
def get_success_url(self):
return reverse('add-amenities', kwargs={'venue_id': self.object.id,})
I suppose it has to do with my template since rendering the form normally, it does prepopulate the model.
Thank you for taking the time!
EDIT:
With Raydel Miranda's answer below I managed to edit the templates for how the form gets presented:
forms.py:
class CustomAmenitiesSelectMultiple(CheckboxSelectMultiple):
"""
CheckboxSelectMultiple Parent: https://docs.djangoproject.com/en/2.1/_modules/django/forms/widgets/#CheckboxSelectMultiple
checkbox_select.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/checkbox_select.html
multiple_input.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/multiple_input.html
checkbox_option.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/checkbox_option.html
input_option.html: https://github.com/django/django/blob/master/django/forms/templates/django/forms/widgets/input_option.html
"""
template_name = "forms/widgets/custom_checkbox_select.html"
option_template_name = 'forms/widgets/custom_checkbox_option.html'
class AmenitiesForm(ModelForm):
class Meta:
model = Venue
fields = ('choices',)
choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=CustomAmenitiesSelectMultiple,)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if kwargs.get('instance'):
initial = kwargs.setdefault('initial', {})
initial['choices'] = [c.pk for c in kwargs['instance'].amenity_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self)
instance.amenity_set.clear()
instance.amenity_set.add(*self.cleaned_data['choices'])
return instance
custom_checkbox_select.html:
{% with id=widget.attrs.id %}
<div class="inline field">
<div {% if id %} id="{{ id }}" {% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}" {% endif %}>
{% for group, options, index in widget.optgroups %}{% if group %}
<div>
{{ group }}
<div>
{% if id %} id="{{ id }}_{{ index }}" {% endif %}>{% endif %}{% for option in options %}
<div class="checkbox">{% include option.template_name with widget=option %}</div>
{% endfor %}{% if group %}
</div>
</div>
{% endif %}{% endfor %}
</div>
</div>
{% endwith %}
custom_checkbox_option.html :
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.wrap_label %} {{ widget.label }}</label>
As requested, also my models.py:
class TimeStampedModel(models.Model):
"""
An abstract base class model that provides self-updating
"created" and "modified" fields.
"""
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class VenueType(TimeStampedModel):
type = models.CharField(max_length=250)
description = models.TextField()
def __str__(self):
return self.type
class Venue(TimeStampedModel):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=250)
type = models.ForeignKey(VenueType, on_delete=models.CASCADE)
total_capacity = models.PositiveIntegerField(default=0)
description = models.TextField(blank=False)
contact_number = PhoneNumberField(blank=True)
contact_email = models.EmailField(blank=True)
published = models.BooleanField(default=False)
def __str__(self):
return self.name
class AmenitiesCategory(TimeStampedModel):
category = models.CharField(max_length=250)
description = models.TextField()
def __str__(self):
return self.category
class Amenity(TimeStampedModel):
category = models.ForeignKey(AmenitiesCategory, on_delete=models.CASCADE)
venues = models.ManyToManyField(Venue, blank=True)
space = models.ManyToManyField(Space, blank=True)
name = models.CharField(max_length=250)
description = models.TextField()
def __str__(self):
return self.name
class Meta:
ordering = ['category']
You said while configuring my initial data and presenting it with {{form.as_p}} everything works as expected, if so, use {{ form.choices }} in order to render that field.
<form method="POST" class="ui form">
{% csrf_token %}
{{form.choices}}
<button type="submit" name="submit" class="ui button primary">Next</button>
</form>
Then, what you need is have a custom CheckboxSelectMultiple with its own template (in case you want a custom presentation to the user), and use it in your form:
Custom CheckboxSelectMultiple could be:
class MyCustomCheckboxSelectMultiple(CheckboxSelectMultiple):
template_name = "project/template/custom/my_checkbox_select_multiple.html"
And in the form:
class AmenitiesForm(ModelForm):
# ...
choices = forms.ModelMultipleChoiceField(Amenity.objects.all(), widget=forms.MyCustomCheckboxSelectMultiple)
# ...
How to implement the template my_checkbox_select_multiple.html, is up to you.
If you're using some Django prior to 1.11, visit this link to learn about a others things you've to do in order to customize a widget template.
Django widget override template
Hope this help!
I have a DetailView, in which I show contents of a post and as well I wanted to add comments functionality to that view. I found 2 ways to do it: combine a DetailView and FormView or make a custom view with mixins. Since I am new to Djanfgo, I went on the 1st way, guided by this answer: Django combine DetailView and FormView but i have only a submit button and no fields to fill on a page.
Here is a necessary code:
#urls.py
from . import views
app_name = 'bankofideas'
urlpatterns = [
path('<int:pk>/', views.DetailView.as_view(), name='idea'),
path('<int:idea_id>/vote', views.vote, name='vote'),
path('<formview', views.MyFormView.as_view(), name='myform')
]
#views.py
class DetailView(generic.DetailView):
model = Idea
template_name = 'bankofideas/detail.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return Idea.objects.filter(pub_date__lte=timezone.now())
def get_context_data(self, **kwargs): # передача формы
context = super(DetailView, self).get_context_data(**kwargs)
context['comment_form'] = CommentForm#(initial={'post':
self.object.pk})
return context
class MyFormView(generic.FormView):
template_name = 'bankofideas/detail.html'
form_class = CommentForm
success_url = 'bankofideas:home'
def get_success_url(self):
post = self.request.POST['post']
Comment.objects.create()
return '/'
def form_valid(self, form):
return super().form_valid(form)
#models.py
class Idea(models.Model):
main_text = models.CharField(max_length=9001, default='')
likes = models.IntegerField(default=0)
pub_date = models.DateTimeField('date published',
default=timezone.now)
def __str__(self):
return self.main_text
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.id:
self.pub_date = timezone.now()
#self.modified = timezone.now()
return super(Idea, self).save(*args, **kwargs)
class Comment(models.Model):
post = models.ForeignKey(Idea, on_delete=models.PROTECT) #
related_name='comments',
user = models.CharField(max_length=250)
body = models.TextField(default=' ')
created = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.body
def get_absolute_url(self):
return reverse('post_detail', args=[self.post.pk])
#forms.py
from .models import Idea, Comment
from django import forms
class IdeaForm(forms.ModelForm):
class Meta:
model = Idea
fields = ('main_text',)
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
exclude = ['post', 'datetime']
#detail.html
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block body %}
<div class="container">
<p>{{ idea.main_text }} {{ idea.likes }} like(s)</p>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'bankofideas:vote' idea.id %}" method="post">
{% csrf_token %}
<input type="submit" name="likes" id="idea.id" value="{{ idea.likes }}" />
</form>
{% for comment in idea.comment_set.all %}
<p>{{comment.body}}</p>
{% empty %}
<p>No comments</p>
{% endfor %}
<form action="{% url "bankofideas:myform" %}" method="post">
{% csrf_token %}
{{ myform. }}
<input type='submit' value="Отправить" class="btn btn-default"/>
</form>
</div>
{% endblock %}
As a result, I have an ability to see post, read all comments, like it, but cannot leave a comment. I tried to rename the form both in view and template, but it didn't work.
The question is: What should i do to bring comment form on the page?