python=2.7, django=1.11.13
In my html I am not able to display my condition choices from my models.py
When filling the form, the user is not able to choose a condition because they are not displayed.
models.py
class Books(models.Model):
book_name = models.CharField(max_length=100)
book_condition = models.ForeignKey('Condition')
def __unicode__(self):
return self.book_name
class Condition(models.Model):
NEW = 'new'
USED = 'used'
COLLECTIBLE = 'collectible'
CONDITION_CHOICES = (
(NEW, 'New'),
(USED, 'Used'),
(COLLECTIBLE, 'collectible'),
)
book = models.ForeignKey(Books)
condition = models.CharField(max_length=10, choices=CONDITION_CHOICES)
def __unicode__(self):
return self.condition
views.py
def add_book(request):
if request.method == 'GET':
context = {
'form': BookForm()
}
if request.method == 'POST':
form = BookForm(request.POST)
if form.is_valid():
form.save()
context = {
'form': form,
}
return render(request, 'add_book_form.html', context=context)
add_book_form.html
{% extends 'base.html' %}
{% block body %}
<h3>Add Book </h3>
<form action="" method="post">
{% csrf_token %}
{{ form}}
<br/>
<input class="button" type="submit" value="Submit"/>
</form>
{% endblock %}
And this is my form, I'm not sure what I am missing.
form
from django.forms import ModelForm
from .models import Books, Condition
class BookForm(ModelForm):
class Meta:
model = Books
fields = '__all__'
class ConditionForm(ModelForm):
class Meta:
model = Condition
fields = '__all__'
The form you're passing to the view is a BookForm, the BookForm contains a ForeignKey field to the Condition model, so the options in the select will be instances of the Condition model.
You would need to preemptively create the Condition model instances, via the admin interface or the shell, and then you could see the conditions on the select, but that won't help, because your Condition instance needs to be associated to a Book, and that makes me think your software is badly designed.
Let me propose a solution:
class Book(models.Model):
"""
This model stores the book name and the condition in the same
table, no need to create a new table for this data.
"""
NEW = 0
USED = 1
COLLECTIBLE = 2
CONDITION_CHOICES = (
(NEW, 'New'),
(USED, 'Used'),
(COLLECTIBLE, 'Collectible'),
)
name = models.CharField(max_length=100)
condition = models.SmallIntegerField(choices=CONDITION_CHOICES)
def __unicode__(self):
return "{0} ({1})".format(self.book_name, self.condition)
class BookForm(ModelForm):
class Meta:
model = Book
fields = '__all__'
Now the conditions are saved as an integer (like it would be if you used foreign keys) and your software is easier to understand and develop.
Try to use Django widgets. For example:
class BookForm(forms.Form):
categories = (('Adventure', 'Action'),
('Terror', 'Thriller'),
('Business', 'War'),)
description = forms.CharField(max_length=9)
category = forms.ChoiceField(required=False,
widget=forms.Select,
choices=categories)
Related
I am fairly new to Django and struggling a bit on how to get the primary keys from form input on a multiform view. I cannot get the keys into the database.
I have 3 models: Human, Human_notes, and Location. My form is made of these separate models as forms on one view. I thought I should collect the primary key from each form once saved and apply that to the next form data as a foreign key...
<form action="human-add" method="POST">
{% csrf_token %}
{{ human_form.as_p }}
{{ location_form.as_p }}
{{ human_notes_form.as_p }}
<button type="submit" class="save btn btn-success">Save</button>
</form>
Human has FKs to Location...:
class Human(models.Model):
intaker = models.ForeignKey(User, default=None, on_delete=models.SET_NULL, null=True)
location = models.OneToOneField('Location', on_delete=models.SET_NULL, null=True, related_name='humans')
Human_notes has FK to Human...(maybe this will become an FK in Human but originally thought many notes for one human) :
class HumanNotes(models.Model):
human = models.ForeignKey(Human, on_delete=models.SET_NULL, null=True, related_name='humans_notes')
My view is:
def human_add(request):
if request.method == 'POST':
human_form = HumanForm(request.POST)
location_form = LocationForm(request.POST)
human_notes_form = HumanNotesForm(request.POST)
if human_form.is_valid() and location_form.is_valid() and human_notes_form.is_valid():
human_form.save(commit=False)
location_form.save()
locationKey = location_form.id
human_notes_form.save(commit=False)
human_notes_form.intaker = request.user
human_notes_form.save()
noteKey = human_notes_form.id
human_form.location = locationKey
human_form.note = noteKey
human_form.intaker = request.user
human_form.save()
return redirect('/intake/success-form')
else:
context = {
'human_form': human_form,
'location_form': location_form,
'human_notes_form': human_notes_form,
}
else:
context = {
'human_form': HumanForm(),
'location_form': LocationForm(),
'human_notes_form': HumanNotesForm(),
}
return render(request, 'intake/human-add.html', context)
The only error I am getting is that 'LocationForm' object has no attribute 'id' - but I even added it explicitly (thought I should not have to and don't want it visible):
class HumanNotesForm(forms.ModelForm):
class Meta:
model = HumanNotes
fields = ['id','intaker','note']
class LocationForm(forms.ModelForm):
class Meta:
model = Location
fields = ['id','st1','st2','cty','county','state','zip','lat','long','img','img_nm','img_id']
Any guidance appreciated.
This did the trick....as did getting excellent guidance from forum.djangoproject.com. Needed to understand the diff between forms and models.
def human_add(request):
if request.method == 'POST':
human_form = HumanForm(request.POST)
location_form = LocationForm(request.POST)
human_notes_form = HumanNotesForm(request.POST)
if human_form.is_valid() and location_form.is_valid() and human_notes_form.is_valid():
loc = location_form.save()
hum = human_form.save(commit=False)
humnote = human_notes_form.save(commit=False)
hum.intaker = request.user
hum.location = loc #.id NOTE YOU NEED THE ENTIRE INSTANCE
human_form.save()
humnote.intaker = request.user
humnote.human = hum #.id NOTE YOU NEED THE ENTIRE INSTANCE
human_notes_form.save()
return redirect('/intake/success-form')
else:
context = {
'human_form': human_form,
'location_form': location_form,
'human_notes_form': human_notes_form,
}
else:
context = {
'human_form': HumanForm(),
'location_form': LocationForm(),
'human_notes_form': HumanNotesForm(),
}
return render(request, 'intake/human-add.html', context)
I'm trying to display some information on a SELECT tag on my template that passes through a for loop and can't seem to find a how to. The examples I find on the internet aren't dynamics (from another table) and I'm really struggling on that. Bellow are my codes, hope you guys can help me on this.
This is my models.py
class Books(models.Model):
[...]
category_cod_category = models.ForeignKey(
'Category',
on_delete = models.CASCADE
)
class Meta:
db_table = 'Books'
class Category(models.Model):
category_name = models.CharField(
max_length = 45
)
def __str__(self):
return '%s' % self.category_name
class Meta:
db_table = 'Category'
This is my views.py and this bit of code is from another template where I have a button for editing.
def edit(request,id):
book = Books.objects.get(id=id)
return render(request,'edit.html',{'edit_book':book})
This is my template edit.html where i need the category of the books to be displayed. Every other field is OK.
<form method="POST" class="post-form" action="/update/{{ book.id }}">
{% csrf_token %}
[...]
<div class="container">
<label>Categoria do Livro:</label>
<select class="selectpicker form-control" data-live-search="true">
{% for books in Books %}
<option value="{{ edit_livro.category_cod_category}}">{{ edit_livro.category_cod_category}}</option>
{% endfor %}
</select>
<div>
[...]
Can you guys help me? Or send me a place where I can learn how to solve this?
Sorry for any misspellings, I'm not a native english writer.
Thanks!
EDIT:
I'm using ModelForms for my insert template, It's listed below:
class BooksForm(forms.ModelForm):
class Meta:
model = Books
fields = [ ...,'category_cod_category', ... ]
# Step:1
# Create url for views (listing, create, update)
urls.py
path(r'book-listing/', BookListView.as_view(), name='book-info-listing'),
path(r'book-info-create/', BookCreateView.as_view(), name='create-book-info'),
path(r'book-info-edit/<int:id>', BookUpdateView.as_view(), name='edit-book-info'),
# Step:2
# Create form for your model
forms.py
# You just need to set the queryset for your field of form, if you want to get the select tag for all your category
class BooksForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['category_cod_category'].queryset = Category.objects.all()
class Meta:
model = Books
fields = [ ...,'category_cod_category', ... ]
# Step:3
# Create views for your model
views.py
# For Listing a book info
class BookListView(generic.ListView):
model = Books
def get_queryset(self):
books = self.model.objects.all()
return books
# For Creating a new book
class BookCreateView(generic.CreateView):
model = Books
form_class = BookForm
template_name = 'project_app/book-form.html'
def form_valid(self, form):
form.save(self.request)
return redirect('book-info-listing')
# For Editing or updating a book
class BookUpdateView(generic.UpdateView):
model = Books
form_class = BookForm
template_name = 'project_app/book-form.html'
success_url = reverse_lazy('book-info-listing')
# get the object
def get_object(self, *args, **kwargs):
book_info = get_object_or_404(Books, pk=self.kwargs['id'])
return book_info
def form_valid(self, form):
form.save(self.request)
return redirect('book-info-listing')
# Step:4 Render form in your template
book-form.html
<form method="POST" class="books_form">
{% csrf_token %}
{{form.category_cod_category}}
</form>
I have created this application but the problem I face now is one that has kept me up all night. I want users to be able to see and select only their own categories when they want to create a post. This is part of my codes and additional codes would be provided on request
category model
class Category(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1,related_name='categories_created')
name = models.CharField(max_length = 120)
slug = models.SlugField(unique= True)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
post model
class Post(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1,related_name='posts_created') #blank=True, null=True)
title = models.CharField(max_length = 120)
slug = models.SlugField(unique= True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category_created', null= True)
addition codes would be provided immediately on request. Thanks
View.py in post app
def create(request):
if not request.user.is_authenticated():
messages.error(request, "Kindly confirm Your mail")
#or raise Http404
form = PostForm(request.POST or None, request.FILES or None)
user = request.user
categories = Category.objects.filter(category_created__user=user).distinct()
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
create_action(request.user, 'Posts', instance)
messages.success(request, "Post created")
return HttpResponseRedirect(instance.get_absolute_url())
context = {
"form": form,
}
template = 'create.html'
return render(request,template,context)
Form
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = [
"title",
"content",
"category",
]
html
{% if form %}
<form method="POST" action="" enctype="multipart/form-data">{% csrf_token %}
{{ form|crispy|safe }}
<input type="submit" name="submit" value="Publish">
</form>
{% endif %}
What you need to do is well-described here. Basically, you are using ModelForm which generates the form from your model. Your model doesn't know anything about filtering by user, so you will need to explicitly add a QuerySet to your form that only shows the desired categories. Change your "categories = ..." line to something like:
form.category.queryset = Category.objects.filter(user=user)
form.fields['category'].queryset = Category.objects.filter(user=user)</strike>
I have a problem when I want to save the objects such as the Tags, and always returned an error because form validation.
Select a valid choice. hello is not one of the available choices.
Here, I want to implement the select input dynamically which customs additional value from the users creation.
For the frontend demo, like this snippet: https://jsfiddle.net/agaust/p377zxu4/
As conceptually, the tags input provide available tags that already created before... But, the important thing is the Users are allowed to create additional tags what they wants.
1. here is my models.py
#python_2_unicode_compatible
class Tag(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
def __str__(self):
return self.title
class Meta:
verbose_name = _('Detail Tag')
verbose_name_plural = _('Tags')
#python_2_unicode_compatible
class Thread(TimeStampedModel):
title = models.CharField(max_length=200)
....
tags = models.ManyToManyField(
Tag, blank=True, related_name='tags_thread')
2. forms.py
from myapp.models import (Tag, Thread)
class ThreadForm(forms.ModelForm):
description = DraceditorFormField()
tags = forms.ModelMultipleChoiceField(
to_field_name='slug', # set the value to slug field, not pk/id
required=False,
label=_('Additional tags'),
help_text=_('Sparate by comma to add more than once, or select from available tags'),
queryset=Tag.objects.all(),
widget=forms.SelectMultiple(attrs={
'placeholder': _('Additional tags'),
'class': 'ui search fluid dropdown dropdown-add-tags'
})
)
class Meta:
model = Thread
fields = '__all__'
exclude = [
'author', 'topic', 'rating',
'created', 'modified'
]
widgets = {
'title': forms.TextInput(attrs={'placeholder': _('Title')})
}
def clean(self):
# this condition only if the POST data is cleaned, right?
cleaned_data = super(ThreadForm, self).clean()
print(cleaned_data.get('tags')) # return queryset of tags
3. views.py
def save_tagging(post_getlist_tags):
"""
return value list of slugs from the filed of `tags`.
allow to create if the tag is doesn't exist.
this function bassed on slug field.
:param `post_getlist_tags` is request.POST.getlist('tags', [])
"""
cleaned_slug_tags = []
for value in post_getlist_tags:
slug = slugify(value)
if Tag.objects.filter(slug=slug).exists():
cleaned_slug_tags.append(slug)
else:
tag = Tag.objects.create(title=value, slug=slug)
cleaned_slug_tags.append(tag.slug)
return cleaned_slug_tags
#login_required
def thread_new(request, topic_slug):
....
topic = get_object_or_404(Topic, slug=topic_slug)
if request.method == 'POST':
form = ThreadForm(request.POST, instance=Thread())
if form.is_valid():
initial = form.save(commit=False)
initial.author = request.user
initial.topic = topic
# set tagging, this will not being executed because error form validation
initial.tags = save_tagging(request.POST.getlist('tags', []))
initial.save()
form.save()
else:
# forms.errors # goes here..
Let checkout what I have when I typing the additional tags,
<select multiple="multiple" id="id_tags" name="tags" placeholder="Additional tags">
<option value="hello" class="addition">hello</option>
<option value="albacore-tuna" class="addition">albacore-tuna</option>
<option value="amur-leopard" class="addition">amur-leopard</option>
<option value="This other once" class="addition">This other once</option>
</select>
This why I implement my field of tags in the form is like this...
tags = forms.ModelMultipleChoiceField(
to_field_name='slug'
....
)
I would be very appreciated for the answers... :)
Update Solved
Thank you so much for #Resley Rodrigues for help.. Finally, I got it without the form field... only handled in the views and the template.
def save_tagging(post_getlist_tags):
"""
return objects list of tags.
allow to create if the tag is doesn't exist.
this function bassed on slug field.
:param `post_getlist_tags` is request.POST.getlist('fake_tags', [])
"""
cleaned_tags = []
for value in post_getlist_tags:
slug = slugify(value)
if Tag.objects.filter(slug=slug).exists():
tag = Tag.objects.filter(slug=slug).first()
cleaned_tags.append(tag)
else:
# makesure the slug is not empty string.
# because I found the empty string is saved.
if bool(slug.strip()):
tag = Tag.objects.create(title=value, slug=slug)
tag.save()
cleaned_tags.append(tag)
return cleaned_tags
#login_required
def thread_new(request, topic_slug):
....
if request.method == 'POST':
form = ThreadForm(request.POST, instance=Thread())
if form.is_valid():
initial = form.save(commit=False)
initial.author = request.user
....
form.save()
# set tagging after created the object
saved_tags = save_tagging(request.POST.getlist('fake_tags', []))
initial.tags.add(*saved_tags)
and the templates.html using field named by fake_tags, I just think it should hasn't crash with field that already named by tags.
<select name="fake_tags" multiple="multiple" class="ui search fluid dropdown dropdown-add-tags"></select>
<script>
$(document).ready(function() {
$('.ui.dropdown.dropdown-add-tags').dropdown({
placeholder: '{% trans "Additional tags" %}',
allowAdditions: true,
minCharacters: 3,
apiSettings: {
url: 'http://api.semantic-ui.com/tags/{query}'
}
});
});
</script>
For edit mode, add the following these lines below after end-tag of $('.ui.dropdown.dropdown-add-tags').dropdown({...});
thread is instance object.
var items = [{% for tag in thread.tags.all %}"{{ tag.title }}"{% if not forloop.last %},{% endif %}{% endfor %}];
$('.ui.dropdown.dropdown-add-tags').dropdown(
'set selected', items
);
I am trying to filter M2M queryset with autocomplete-light support.
I can get the filter working with the built-in ModelForm. Here is simplified version of code that works flawlessly without autocomplete:
models.py:
class FEmodel(models.Model):
name = models.Charfield(max_length=200)
class Workspace(models.Model):
name = models.Charfield(max_length=200)
fem = models.ForeignKey(FEmodel)
class Element(models.Model):
EID = models.PositiveIntegerField()
fem = models.ForeignKey(FEmodel)
class Panel(models.Model):
workspace = models.ForeignKey(Workspace)
elements = models.ManyToManyField(Element)
forms.py:
class PanelForm(forms.ModelForm):
def __init__(self,*args,**kwargs):
super(PanelForm,self).__init__(*args,**kwargs)
ws = self.instance.workspace
self.fields['elements'].queryset = Element.objects.filter(fem=ws.fem)
class Meta:
model = Panel
fields = ('__all__')
views.py:
#login_required(login_url='/login/')
def panel_edit(request, pk, id=None):
workspace = get_object_or_404(Workspace, pk=pk)
if id:
panel = get_object_or_404(Panel, pk=id)
else:
panel = Panel(workspace = workspace)
if request.method == 'POST':
form = PanelForm(request.POST, instance=panel)
if form.is_valid():
panel = form.save(commit=True)
return panels(request, pk)
else:
print form.errors
else:
form = PanelForm(instance=panel)
return render(request, 'structures/Panel/panel_edit.html', {'form': form, 'panel': panel, 'workspace': workspace})
urls.py:
...
url(r'^workspace/(?P<pk>[0-9]+)/panel/new/$', views.panel_edit, name='panel_edit'),
...
panel_edit.html:
...
<form method="POST" class="form-horizontal">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit"> Save</button>
{% endbuttons %}
</form>
....
And here is autocomplete version which i could not get working:
autocomplete_light_registry.py
class ElementAutocomplete(acl.AutocompleteModelBase):
search_fields = ['EID']
acl.register(Element, ElementAutocomplete)
forms.py:
import autocomplete_light.shortcuts as acl
class PanelForm(acl.ModelForm):
def __init__(self,*args,**kwargs):
super(PanelForm,self).__init__(*args,**kwargs)
ws = self.instance.workspace
self.fields['elements'].queryset = Element.objects.filter(fem=ws.fem)
class Meta:
model = Panel
fields = ('__all__')
This version throws no errors but does not provide Element choices filtered by form.instance.ws.fem attribute. Instead it gives all Element objects.
What am i doing wrong?
edit 1:
in forms.py super(Panel,self) was corrected as super(PanelForm,self)
indent typos were corrected
edit 2: required portions of url, view and template added
edit 3:
According to #jpic's answer here is the solution:
added to panel_edit.html:
{% block bootstrap3_extra_head %}
{{ block.super }}
<script type="text/javascript">
$( document ).ready(function() {
elements_autocomplete = $('input[name=elements-autocomplete]').yourlabsAutocomplete()
elements_autocomplete.data['ws_pk'] = {{ form.instance.workspace.pk }}
});
</script>
{% endblock %}
autocomplete_light_registry.py:
import autocomplete_light as acl
class ElementAutocomplete(acl.AutocompleteModelBase):
search_fields = ['EID']
model = Element
def choices_for_request(self):
ws = Workspace.objects.get(pk=self.request.GET.get('ws_pk', None))
self.choices = self.choices.filter(fem=ws.fem)
return super(ElementAutocomplete, self).choices_for_request()
acl.register(ElementAutocomplete)
forms.py:
class PanelForm(acl.ModelForm):
class Meta:
model = Panel
fields = ('__all__')
The autocomplete JS object needs the pk value to pass it on to the view which calls the Python autocomplete object, then you can filter on the instance pk in choices_for_request() method of the python autocomplete object.
One way to get the js autocomplete object is to get it from the input element itself with the jQuery plugin, ie.:
elements_autocomplete = $('input[name=elements-autocomplete]').yourlabsAutocomplete()
Ensure that this is called after jquery-autocomplete-light JS is loaded.
Then, add the fk to its data:
elements_autocomplete.data['panel_pk'] = {{ form.instance.pk }}
In choices_for_request(), you probably figured it out by now:
def choices_for_request(self):
choices = super(ElementAutocomplete, self).choices_for_request()
panel_pk = request.GET.get('panel_pk', None)
if panel_pk and panel_pk.isdigit():
choices = choices.filter(panel__pk=panel_pk)
return choices
Actually, that's very easy to test, in your browser, open the JS console and run: $('input[name=elements-autocomplete]').yourlabsAutocomplete().data['foo'] = 'bar' and you'll see that subsequent requests made by the autocomplete script will add &foo=bar to the URL it its, making it available to choices_for_request through self.request !