Recent values in CharField - django

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>

Related

django form field displayed out of order

I have this form and model for a group:
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = ('leader', 'description', 'size', 'max_size', 'motto')
widgets = {
'size': forms.CheckboxInput(attrs={'id': 'size'}),
'max_size': forms.TextInput(attrs={'type': 'hidden', 'id': 'maxSize'}),
}
The creator of the group has an option to check yes for size and on doing so, I used javascript to change the type of max_size to show.
In my create_group.html template:
<script>
let size = document.getElementById('size')
let maxSize = document.getElementById('maxSize')
let checked = false
size.onclick = () => {
checked = !checked
if (checked === true) {
maxSize.type = 'show'
} else {
maxSize.type = 'hidden'
}
}
</script>
Now, this works fine, the only problem is that the fields are displayed out of order.
When the page loads, max_size is false and its field is not displayed. Which is good. However, when the user checks that group has a size, and, subsequently, the max_size has a display of show, the field shows up after the motto field and not in its correct order according to fields = ('leader', 'description', 'size', 'max_size', 'motto').
Furthermore, the max_size field is included inside the motto element itself and not as its own field:
vs. the other fields which are all in their own <p></p>.
I'm guessing that {{form.as_p}} etc. render all the visible fields first, then the hidden ones.
You can explicitly render the fields in the order you want in your template. Rather than hardcoding the order in your template, maybe this (I've never tried this):
FIELD_ORDER = ( ('leader', 'description', 'size', 'max_size', 'motto')
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = FIELD_ORDER
Pass to your template a list of fields explicitly in the order you want:
fields_in_order = [ form[x] for x in FIELD_ORDER ]
In the template
{% for field in fields_in_order %}
{{field}}
{% endfor %}
Or, you can make the hiding of this field something done by JS
If anyone else comes across this issue, it's also possible to just use js and css. Right now, I'm using javascript to see if size is checked and if it is then maxSize.style.display = 'block' vs maxSize.style.display = 'none' if size isn't checked.
Then I had the issue of django's form label still being visible. To fix that I saw an answer on dev.to which you can see for yourself.
My issue now is that I don't know how to add a label that is only visible when the form field is visible.

Django Autocomplete Light - FK Field Results not Forwarding

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.

Django: retrieving ManyToManyField objects with minimum set of queries

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 %}

m2m through : accessing intermediate table from templates

When a ManyToMany relationship has extra data via a through table, how can you get to the data in a template? From a view I can get the data if I supply parameters:
class Category(models.Model):
title = models.CharField(max_length=1024,null=True,blank=True)
entry = models.ManyToManyField(Entry,null=True,blank=True,
related_name='category_entry',
through='CategoryEntry',
)
class CategoryEntry(models.Model):
category = models.ForeignKey(Category)
entry = models.ForeignKey(Entry)
votes = models.IntegerField(null=False, default=0)
def category_detail(request, pk):
category = models.Category.objects.select_related().get(pk=pk)
entries = category.entry.order_by('-temp_sort_order').filter(temp_sort_order__gte=0)
for entry in entries:
assert isinstance(entry, models.Entry)
ce = models.CategoryEntry.objects.get(entry=entry, category=category)
pprint('Show votes as a test: ' + ce.votes) #OK
pprint('entry title: ' + entry.title) #OK
pprint('entry votes: ' + str(entry.category_entry.votes)) #BAD
pprint('entry votes: ' + str(entry.entry.votes)) #BAD
....
But templates can't supply parameters to methods.
The documentation at https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships is silent on templates. Using using for entry in category.category_entry_set.all gives 'Category' object has no attribute 'category_entry_set'. category.category_entry.all does not work either.
Ultimately I want to display the extra data in a template:
{% for entry in entries %}
<ul>
<li>Title: {{ entry.title }} Votes: {{ entry.category_entry.votes }} {{ entry.entry.votes }}</li>
</ul>
{% endfor %}
If you have a category instance in template:
category.entry.all -> list of entries
If you have an entry instance in template:
entry.category_entry.all -> list of categories
You should call M2M fields in plural form,
then you will have a more readable code
category.entries.all
%model%_set syntax (or related name, if you've specified it) is using to access to model trough a backward relationship.
https://docs.djangoproject.com/en/1.4/topics/db/queries/#following-relationships-backward
But how do I get the 'votes' associated with the m2m instance? – Bryce
I suggest you the following way:
class Category(models.Model):
title = models.CharField(max_length=1024,null=True,blank=True)
entries = models.ManyToManyField(Entry,null=True,blank=True,
related_name='categories',
through='CategoryEntry',
)
class CategoryEntry(models.Model):
category = models.ForeignKey(Category, related_name='category_entries')
entry = models.ForeignKey(Entry)
votes = models.IntegerField(null=False, default=0)
def category_detail(request, pk):
category = models.Category.objects.select_related().get(pk=pk)
category_entries = category.category_entries.filter(entry__temp_sort_order__gte=0).order_by('-entry__temp_sort_order')
for category_entry in category_entries:
# category_entry is an instance of the model CategoryEntry
pprint('category entry votes: ' + str(category_entry.votes))
pprint('entry title: ' + category_entry.entry.title)
....
HOW TO
entry = Entry.objects.get(pk=1)
entry.categories.all() # list of categories (here we work through related name of the field entries)
category = Category.objects.get(pk=1)
category.entries.all() # list of entries (here we work through m2m field entries)
category.category_entries.all() # list of CategoryEntry objects (through related name category_entries of the field category in model CategoryEntry)
Updating my answer, i mistakenly put related manager on wrong model, in your case, like Andrey said, the correct way to get entries from category is:
category.entry.all()
Now, to address your iteration and ordering question. In python it will look like this:
for ce in category.categoryentry_set.order_by('-votes'):
print ce.entry, ce.votes
This will give you entries in each category ordered by votes. To get this to template you can just save a queryset category.categoryentry_set.order_by('-votes') into variable and iterate over it.
Here's an ugly ugly hack that works. After the filter and sort, process the list and append the extra model fields. The templates now have easy access:
entries = category.entry.order_by('-temp_sort_order').filter(temp_sort_order__gte=0)
for entry in entries:
assert isinstance(entry, models.Entry)
ce = models.CategoryEntry.objects.get(entry=entry, category=category)
entry.xxx_votes = mark_safe(ce.votes) # use {{ entry.xxx_votes to access }}
entry.xxx_ce = ce # Use {{ entry.ce.votes to access }}
return render_to_response('category.html')
Hopefully someone can provide a better answer, or suggest an improvement to django itself. This solution does not allow me to sort: category.entry.order_by('-category_entry.votes')

Django Admin: How to dynamically insert values into the add inline form

Let's say I have this model:
class Foo(models.Model):
bar = models.ForeignKey(Bar)
currency = models.ForeignKey(Currency) # currency is just an example
is_active = models.BooleanField()
Now suppose Foo is an Inline of Bar. And I would always want to indicate a value on each currency? If I could replace those currency drop down menus with a widget that just returns the name as text with a hidden field.
So for example, on an add page, instead of having the inline show this:
- currency drop down menu is_active checkbox
- currency drop down menu is_active checkbox
- currency drop down menu is_active checkbox
have it show this:
- currency name 1 is_active checkbox
- currency name 2 is_active checkbox
- currency name 3 is_active checkbox
- currency name 4 is_active checkbox
- currency name 5 is_active checkbox
What would be the right approach to accomplish that? I assume I would have to override some form class method. Thanks.
If you always want to use the same currency:
currency = models.ForeignKey(Currency, default = lambda: Currency.objects.get(...)
If you want to change the currency on the fly subclass the form and override init where you can do your magic on self.fields['currency'].
If you would like to hide the field, use widget = forms.HiddenInput() on that field in the form class.
I think that answers your question, but not your real problem. Use [django-currencies][1] for flexible handling of currencies.
[1]: http://code.google.com/p/django-currencies/ django-currencies
The first problem to solve here is pre-generation of the appropriate linked Foos for each Bar. You could do that in a custom save() method on Bar, or using signals, or in a Bar factory method on a BarManager...
Once that's done, I think your admin problem can be solved like this:
class FooInline(admin.TabularInline):
formfield_overrides = {models.ModelChoiceField: {'widget': ReadOnlyWidget}}
model = Foo
extra = 0
Where you'd use a custom ReadOnlyWidget like the one here.
I solved it with javascript.
Insert the below into the top of tabular or stacked template. It assumes the name column is named name.
{% with inline_admin_formset.opts as i %}
{% if i.pre_filled %}
<script language="JavaScript" type="text/javascript">
jQuery(function($) {
var pre_values = [
{% for name in i.pre_filled %}
{% if forloop.last %}
[{{ name.id }}, "{{ name.name }}"]
{% else %}
[{{ name.id }}, "{{ name.name }}"],
{% endif %}
{% endfor %}
];
$.each( pre_values,
function( i, value ){
$('div.inline-group div.tabular').each(function() {
$("#id_{{ i.verbose_name|lower|cut:" " }}_set-" + i + "-{{ i.pre_field }}").after(value[1]);
$("#id_{{ i.verbose_name|lower|cut:" " }}_set-" + i + "-{{ i.pre_field }}").val(value[0]).hide();
$("#lookup_id_{{ i.verbose_name|lower|cut:" " }}_set-" + i + "-{{ i.pre_field }}").hide();
$("strong").hide();
});
}
);
});
</script>
{% endif %}
{% endwith %}
And this goes in your inline.py:
class MyCustomInline(admin.TabularInline):
pre_filled = []
pre_field = ''
def get_fieldsets(self, request, obj=None):
if not obj and self.pre_filled and self.pre_field:
count = self.pre_filled.count()
self.extra = count
self.max_num = count
if self.raw_id_fields:
self.raw_id_fields.append(self.pre_field)
else:
self.raw_id_fields = [self.pre_field]
return super(MyCustomInline, self).get_fieldsets(request, obj)
class FooInline(MyCustomInline):
model = Foo
pre_filled = Currency.objects.all()
pre_field = 'currency'