I'm following along in the DAL documentation to add a filtered field to my form, but the forwarding isn't working to connect one field to the other:
Forms.py
class PurchaseForm(forms.ModelForm):
commodity = forms.ModelChoiceField(
queryset=Commodity.objects.all(),
widget=autocomplete.ModelSelect2(url='commodity-autocomplete'),
required=False,
)
class Meta:
model = Purchase
fields = ["variety"]
widgets = {
'variety': autocomplete.ModelSelect2(url='variety-autocomplete', forward=['commodity'],
}
Views.py
class VarietyAutocompleteView(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Variety.objects.all()
commodity = self.forwarded.get('commodity', None)
print("Commodity:" + str(commodity))
if commodity:
qs = qs.filter(commodity=commodity)
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs
I'd like my Variety choices to be filtered by their foreign key relationship to Commodity objects. Both autocomplete fields are working on their own just fine, but the choice from the commodity field is not being forwarded to the VarietyAutocompleteView (my print command prints Commodity:None). Is this perhaps because I am passing a foreign key object? Or have I set this up incorrectly somehow?
I had to scrap DAL and move to Bootstrap Combobox. It turned out to be really easy to implement, provided you are using the Bootstrap libraries.
This is how it is set up:
Add class combobox to the select widget:
forms.py
from django import forms
from Business.models import Company, Branch
from .models import Variety
class PurchaseForm(forms.ModelForm):
variety = forms.ModelChoiceField(
queryset=Variety.objects.all(),
widget=forms.Select(attrs={'class': 'combobox'}),
required=False
)
class Meta:
model = Purchase
fields = [
"invoice", "contract_date", ...
]
Then, insert the simplest javascript snip ever:
inventory_report.html
....
<td style="padding-bottom: 10px">
<div>Supplier:</div>
<div>{{ view.purchase_form.supplier }}</div>
</td>
....
{% block scripts %}
<script type="text/javascript" src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script type="text/javascript" src="{% static 'js/bootstrap-combobox.js' %}"></script>
$(document).ready(function(){
// Set Text Autofields on New Contract
$('.combobox').combobox();
});
</script>
{{ view.purchase_form.media }}
{% endblock %}
That's all there is to it.
I'll add for anyone else like me who is thinking "yeah, but I still really don't want to change" - that in practice the line:
'variety': autocomplete.ModelSelect2(url='variety-autocomplete', forward=['commodity'])
Really did not work for me either, but changing it to something like:
'variety': autocomplete.ModelSelect2(url='variety-autocomplete', forward=('commodity', ))
Did work. Notice that it's a tuple so you need to have a "," there to make it work, as it's supposed to be an iterable value.
I didn't dig much further into why an array is part of the docs and yet doesn't work, but that was the critical change that make my variant work.
Also - I was using a formset (so there were prefixes) and that proved to not be a problem.
Related
I would like to use django-filter for BooleanField(blank=True, null=True). When I use it out of the box, the form generates three options: Unknown (no filtering), Yes (True) and No (False). However, I need a fourth option for None so that the filter specifically selects those records with value None.
My model (relevant part):
class Record(Model):
accepted = BooleanField(blank=True, null=True, verbose_name=_("Accepted?"))
My filter (relevant part):
class RecordFilter(FilterSet):
class Meta:
model = Record
fields = OrderedDict((
("accepted", ["exact"]),
))
My view:
class RecordList(LoginRequiredMixin, FilterView):
model = Record
paginate_by = 25
template_name = "record/record_list.html"
filterset_class = RecordFilter
My template (relevant part):
<form method="get" role="form" class="form">
{{ filter.form.as_p }}
<button>{% trans "Filter" %}</button>
</form>
How can I achieve the desired behavior?
This can be solved by constructing a ChoiceFilter with the correct arguments, as a user demonstrated here.
That said, the framework should fully support NullBooleanFields, and I've opened an issue to resolve this.
Using a GenericRelation to map Records to Persons, I have the following code, which works correctly, but there is a performance problem I am trying to address:
models.py
class RecordX(Record): # Child class
....
class Record(models.Model): # Parent class
people = GenericRelation(PersonRecordMapping)
def people_all(self):
# Use select_related here to minimize the # of queries later
return self.people.select_related('person').all()
class Meta:
abstract = True
class PersonRecordMapping(models.Model):
person = models.ForeignKey(Person)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class Person(models.Model):
...
In summary I have:
RecordX ===GenericRelation===> PersonRecordMapping ===ForeignKey===> Person
The display logic in my application follows these relationships to display all the Persons mapped to each RecordX:
views.py
#rendered_with('template.html')
def show_all_recordXs(request):
recordXs = RecordX.objects.all()
return { 'recordXs': recordXs }
The problem comes in the template:
template.html
{% for recordX in recordXs %}
{# Display RecordX's other fields here #}
<ul>
{% for map in record.people_all %}{# <=== This generates a query every time! #}
<li>{{ map.person.name }}</li>
{% endfor %}
</ul>
{% endfor %}
As indicated, every time I request the Persons related to the RecordX, it generates a new query. I cannot seem to figure out how prefetch these initially to avoid the redundant queries.
If I try selected_related, I get an error that no selected_related fields are possible here (error message: Invalid field name(s) given in select_related: 'xxx'. Choices are: (none)). Not surprising, I now see -- there aren't any FKs on this model.
If I try prefetch_related('people'), no error is thrown, but I still get the same repeated queries on every loop in the template as before. Likewise for prefetch_related('people__person'). If I try prefetch_related('personrecordmapping'), I get an error.
Along the lines of this answer, I considered doing trying to simulate a select_related via something like:
PersonRecordMapping.objects.filter(object_id__in=RecordX.objects.values_list('id', flat=True))
but I don't understand the _content_object_cache well enough to adapt this answer to my situation (if even the right approach).
So -- how can I pre-fetch all the Persons to the RecordXs to avoid n queries when the page will display n RecordX objects?
Thanks for your help!
Ack, apparently I needed to call both -- prefetch_related('people', 'people__person'). SIGH.
I have some model
class Mod(Model):
f = CharField(max_length=10)
And some form:
class ModForm(ModelForm):
class Meta:
model = Mod
fields = ('f',)
When I display form to user I want to suggest some dropdown with Mod.objects.all() values of 'f'. How to do this in simplest way? I tried specify widgets = {'f': Select}, but it is not editable. I also can use selectize.js to make it selectable with preloading ajax values, but seems like this is too long way.
models.py
class Mod(models.Model):
CHOICE_1 = 'CHOICE 1'
CHOICE_2 = 'CHOICE 2'
FIELD_CHOICES = (
(CHOICE_1, 'The real charfield value'),
(CHOICE_2, 'another charfield value')
)
f = models.CharField(
max_length=10,
choices=FIELD_CHOICES,
default=CHOICE_1
)
forms.py
class ModForm(ModelForm):
class Meta:
model = Mod
fields = ('f',)
end of views.py
return render(request, 'template.html', context={'mod_form': mod_form})
template.html
{{mod_form.as_p}}
This is extracted from working code. Hopefully the obfuscating my specific app out did not make it unclear.
Autocomplete is a simple and nice solution. Thanks to Bilou06.
So, first of all we need to pass list of values, we can do it in view:
args['f_vals'] = Mod.objects.all().values_list("f", flat=True).distinct()[:10]
here we get all flat list of values and perform distinct to filter only uniq values. Then we limit values to 10. Additional benefit here is that we can perform more complex querysets with additional filtering and so on. For example if you have some field you can order it to be real "recent". (You can edit this answer to provide some example).
Next we pass our args into template, and there create autocompleate:
<script>
$(document).ready(function(){
$("#id_f").autocomplete({
source: [{% for v in f_vals %}
"{{v}}",
{% endfor %}
],
minLength: 0,
}).on("focus", function () {
$(this).autocomplete("search", '');
});
}
</script>
On focus event here displays list of recent values immidiately on focus. Also values will be filtered when you typing.
Note that you should include jquery-ui:
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/jquery-ui.min.js' %}"></script>
My code looks like this:
models.py
class Tag(models.Model):
name = models.CharField(max_length=42)
class Post(models.Model):
user = models.ForeignKey(User, related_name='post')
#...various fields...
tags = models.ManyToManyField(Tag, null=True)
views.py
posts = Post.objects.all().values('id', 'user', 'title')
tags_dict = {}
for post in posts: # Iteration? Why?
p = Post.objects.get(pk=[post['id']]) # one extra query? Why?
tags_dict[post['id']] = p.tags.all()
How am I supposed to create a dictionary with tags for each Post object with minimum set of queries? Is it possible to avoid iterating, too?
Yes you will need a loop. But you can save one extra query in each iteration, you don't need to get post object to get all its tags. You can directly query on Tag model to get tags related to post id:
for post in posts:
tags_dict[post['id']] = Tag.objects.filter(post__id=post['id'])
Or use Dict Comprehension for efficiency:
tags_dict = {post['id']: Tag.objects.filter(post__id=post['id']) for post in posts}
If you have Django version >= 1.4 and don't really need a dictionary, but need to cut down the count of queries, you can use this method like this:
posts = Post.objects.all().only('id', 'user', 'title').prefetch_related('tags')
It seems to execute only 2 queries (one for Post and another for Tag with INNER JOIN).
And then you can access post.tags.all without extra queries, because tags was already prefetched.
{% for post in posts %}
{% for tag in post.tags.all %}
{{ tag.name }}
{% endfor %}
{% endfor %}
I'm working on a project where users can vote on collections of books. My models look like this:
class Collection(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=31)
class Book(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
collection = models.ForeignKey(Collection)
vote_choices = ((-1,'Bad'),
(0, 'Not good, Not bad'),
(1,'Good'))
class Vote(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User, related_name='votes')
book = models.ForeignKey(Book)
vote = models.IntegerField(choices=vote_choices)
I need to display a view for the users where they can see all the books in a given collection and vote for those books at once, since this is much more user-friendly than looping over everybook to vote. So I need a formset where every form will be: a book and its vote.
Initially I thought this was a good opportunity to use formsets (I have never used them before, but I'm used to work with forms, modelforms), but I'm running into difficulties on how to create this formset.
I would like to use generic class based views, since, in theory, I could easily create/update all votes easily, but I can't figure out how to do this.
Right now, I don't have a direct relation between the Collection model and the User (from django.contrib.auth.model) model, but I don't mind adding one if this makes things easier. I don't really need one, since every User is linked to a Book through a Vote and every Book is linked to a Collection.
I'm not sure whether I should use formsets or just regular modelforms.
How would you do it? Thanks in advance for any help.
I would recommend not to use django forms for this case. There are some more convenient ways.
First way. Using pure jQuery
Extend your book class:
class Book(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
collection = models.ForeignKey(Collection, related_name='books')
def get_vote(self, user):
vote = Vote.objects.filter(book=self, user=user)
if vote:
return vote.vote
else:
return ''
Your views (you can rewrite it on CBV if you want):
def list_collection(request, collection_id):
collection = Collection.objects.get(pk=id)
user = request.user
books = []
for item in collection.books.all():
book = {
'id': item.pk,
'name': item.name,
'vote': item.get_vote(user)
}
books.append(book)
return render_to_response('colleciton.html', {'books': books})
def set_vote(request):
id = request.GET('id')
vote = int(request.GET('vote'))
user = request.user
book = Book.objects.get(pk=id)
vote = Vote()
vote.user = user
vote.vote = vote
vote.book = book
vote.save()
return HttpResponse()
Your urls:
url(r'^list-collection/', 'list_collection', name='list_collection'),
url(r'^set-vote/', 'set_vote', name='set_vote'),
Your django template:
{% extends 'base.html' %}
{% block content %}
{% for book in collection.books.all %}
<div class="book" data-id="{{ book.id }}">
<div class="book-name">{{ book.name]}</div>
<div class="book-vote" {% if not book.get_vote %}style="display:none"{% endif %}>book.get_vote</div>
<div class="voting" {% if book.get_vote %}style="display:none"{% endif %}>
<button class="vote-up"></button>
<button class="vote-down"></button>
</div>
</div>
{% endfor %}
{% endblock %}
And the javascript is like
$(document).ready(function(){
$('.vote-up, .vote-down').on('click', function(e){
var id, vote, t, book_el;
e.preventDefault();
t = $(e.currentTarget);
book_el = t.parents().parents()
id = book_el.attr('data-id');
if t.hasClass('vote-up'){
vote = 1;
} else {
vote = -1;
}
book_el.find('.book-vote').show();
book_el.find('.book-vote').text(vote);
book_el.find('voting').hide();
$.get("/set-vote", {vote: vote, id: id});
});
});
Second way. Using javascript frameworks like Backbone.js
It makes the whole process much easier. Especially if you plan to add some other features to your app.
From the other hand Backbone requires some time to learn it.
Read the docs, look at the tutorial app TodoMVC or other tutorials.
Maybe you'll find it more appropriate for your task.